diff --git a/.Dockerignore b/.Dockerignore deleted file mode 100644 index f4bf7fcf..00000000 --- a/.Dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!bin \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..630c7988 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +* +!src +!package*.json +!tsconfig*.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..8ce78c0b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @avallete @soedirgo @supabase/sdk diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..6ebae6b8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 00000000..1604962b --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,31 @@ +name: Dependabot auto-merge +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Approve a PR + if: ${{ steps.metadata.outputs.update-type != 'version-update:semver-major' && !startswith(steps.metadata.outputs.new_version, '0.') }} + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable auto-merge for Dependabot PRs + if: ${{ steps.metadata.outputs.update-type != 'version-update:semver-major' && !startswith(steps.metadata.outputs.new_version, '0.') }} + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/canary-comment.yml b/.github/workflows/canary-comment.yml new file mode 100644 index 00000000..69cebc03 --- /dev/null +++ b/.github/workflows/canary-comment.yml @@ -0,0 +1,139 @@ +name: Update Canary PR Comment + +permissions: + pull-requests: write + actions: read + +on: + workflow_run: + workflows: ['Canary Deploy'] + types: [completed] + +jobs: + update-comment: + # Only run on the correct repository + if: github.repository == 'supabase/postgres-meta' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + # Get PR number from the workflow run + - name: Get PR info + id: pr-info + uses: actions/github-script@v7 + with: + script: | + // Get the workflow run details + const workflowRun = context.payload.workflow_run; + + // Find associated PR + const prs = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + head: `${workflowRun.head_repository.owner.login}:${workflowRun.head_branch}` + }); + + if (prs.data.length > 0) { + const pr = prs.data[0]; + + // Check if PR has the deploy-canary label + const labels = pr.labels.map(label => label.name); + const hasCanaryLabel = labels.includes('deploy-canary'); + + if (hasCanaryLabel) { + core.setOutput('pr_number', pr.number); + core.setOutput('found', 'true'); + core.setOutput('has_canary_label', 'true'); + console.log(`Found PR #${pr.number} with deploy-canary label`); + } else { + core.setOutput('found', 'false'); + core.setOutput('has_canary_label', 'false'); + console.log(`Found PR #${pr.number} but it doesn't have deploy-canary label`); + } + } else { + core.setOutput('found', 'false'); + core.setOutput('has_canary_label', 'false'); + console.log('No associated PR found'); + } + + # Extract canary info from the workflow run + - name: Extract canary info + if: ${{ steps.pr-info.outputs.found == 'true' && steps.pr-info.outputs.has_canary_label == 'true' && github.event.workflow_run.conclusion == 'success' }} + id: canary-info + uses: actions/github-script@v7 + with: + script: | + const workflowRun = context.payload.workflow_run; + + // Extract PR number from the branch name or workflow run + const prNumber = '${{ steps.pr-info.outputs.pr_number }}'; + const commitSha = workflowRun.head_sha; + + // Generate the canary tag based on the pattern used in canary-deploy.yml + const canaryTag = `supabase/postgres-meta:canary-pr-${prNumber}-${commitSha}`; + + core.setOutput('tag', canaryTag); + core.setOutput('found', 'true'); + core.setOutput('commit-sha', commitSha); + + console.log(`Generated canary tag: ${canaryTag}`); + + # Find existing comment + - name: Find existing comment + if: ${{ steps.pr-info.outputs.found == 'true' && steps.pr-info.outputs.has_canary_label == 'true' }} + uses: peter-evans/find-comment@v3 + id: find-comment + with: + issue-number: ${{ steps.pr-info.outputs.pr_number }} + comment-author: 'github-actions[bot]' + body-includes: '' + + # Create or update comment based on workflow status + - name: Create or update canary comment + if: ${{ steps.pr-info.outputs.found == 'true' && steps.pr-info.outputs.has_canary_label == 'true' }} + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ steps.pr-info.outputs.pr_number }} + body: | + + ## πŸš€ Canary Deployment Status + + ${{ github.event.workflow_run.conclusion == 'success' && steps.canary-info.outputs.found == 'true' && format('βœ… **Canary image deployed successfully!** + + 🐳 **Docker Image:** `{0}` + πŸ“ **Commit:** `{1}` + + You can test this canary deployment by pulling the image: + ```bash + docker pull {0} + ``` + + You can also set the version in a supabase local project by running: + ```bash + echo "{0}" > supabase/.temp/pgmeta-version + ``` + + Or use it in your docker-compose.yml: + ```yaml + services: + postgres-meta: + image: {0} + # ... other configuration + ``` + + The canary image is available on: + - 🐳 [Docker Hub](https://hub.docker.com/r/supabase/postgres-meta) + - πŸ“¦ [GitHub Container Registry](https://ghcr.io/supabase/postgres-meta) + - ☁️ [AWS ECR Public](https://gallery.ecr.aws/supabase/postgres-meta) + ', steps.canary-info.outputs.tag, steps.canary-info.outputs.commit-sha) || '' }} + + ${{ github.event.workflow_run.conclusion == 'failure' && '❌ **Canary deployment failed** + + Please check the [workflow logs](' }}${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.html_url || '' }}${{ github.event.workflow_run.conclusion == 'failure' && ') for more details. + + Make sure your PR has the `deploy-canary` label and targets the `master` branch.' || '' }} + + --- + Last updated: ${{ github.event.workflow_run.updated_at }} + edit-mode: replace diff --git a/.github/workflows/canary-deploy.yml b/.github/workflows/canary-deploy.yml new file mode 100644 index 00000000..f40f7a0f --- /dev/null +++ b/.github/workflows/canary-deploy.yml @@ -0,0 +1,107 @@ +name: Canary Deploy + +permissions: + contents: read + pull-requests: read + packages: write + id-token: write + +on: + pull_request: + types: [opened, synchronize, labeled] + paths: + - 'src/**' + - 'package.json' + - 'package-lock.json' + - 'tsconfig.json' + - 'Dockerfile' + +jobs: + build-canary: + # Only run if PR has the 'deploy-canary' label, is on the correct repository, and targets master branch + if: | + github.repository == 'supabase/postgres-meta' && + github.event.pull_request.base.ref == 'master' && + contains(github.event.pull_request.labels.*.name, 'deploy-canary') + runs-on: ubuntu-22.04 + timeout-minutes: 30 + outputs: + canary-tag: ${{ steps.meta.outputs.tags }} + pr-number: ${{ github.event.pull_request.number }} + steps: + # Checkout fork code - safe because no secrets are available for building + - name: Checkout code + uses: actions/checkout@v5 + + # Log PR author for auditing + - name: Log PR author + run: | + echo "Canary deploy triggered by: ${{ github.event.pull_request.user.login }}" + echo "PR #${{ github.event.pull_request.number }} from fork: ${{ github.event.pull_request.head.repo.full_name }}" + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies and build + run: | + npm clean-install + npm run build + + # Generate canary tag + - id: meta + uses: docker/metadata-action@v5 + with: + images: | + supabase/postgres-meta + public.ecr.aws/supabase/postgres-meta + ghcr.io/supabase/postgres-meta + tags: | + type=raw,value=canary-pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} + type=raw,value=canary-pr-${{ github.event.pull_request.number }} + + - uses: docker/setup-qemu-action@v3 + with: + platforms: amd64,arm64 + - uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.PROD_AWS_ROLE }} + aws-region: us-east-1 + + - name: Login to ECR + uses: docker/login-action@v3 + with: + registry: public.ecr.aws + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push canary image + uses: docker/build-push-action@v6 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: | + org.opencontainers.image.title=postgres-meta-canary + org.opencontainers.image.description=Canary build for PR #${{ github.event.pull_request.number }} + org.opencontainers.image.source=${{ github.event.pull_request.head.repo.html_url }} + org.opencontainers.image.revision=${{ github.event.pull_request.head.sha }} + canary.pr.number=${{ github.event.pull_request.number }} + canary.pr.author=${{ github.event.pull_request.user.login }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9091173a..c531213f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,37 +7,92 @@ on: - master workflow_dispatch: +permissions: + contents: read + +# Cancel old builds on new commit for same workflow + branch/PR +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: test: - name: Test / OS ${{ matrix.platform }} / Node ${{ matrix.node }} - strategy: - fail-fast: false - matrix: - platform: [ubuntu-latest] - node: ['12'] + name: Test + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v5 - runs-on: ${{ matrix.platform }} + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' - steps: - - uses: actions/checkout@v2 + - run: | + npm clean-install + npm run check + npm run test - - name: Set up DB - run: docker-compose up -d + - uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: coverage/lcov.info - - uses: actions/cache@v1 + prettier-check: + name: Prettier check + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v5 + + - name: Setup node + uses: actions/setup-node@v4 with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- + node-version-file: '.nvmrc' + + # Installing all dependencies takes up to three minutes, hacking around to only installing prettier+deps + - name: Download dependencies + run: | + rm package.json + rm package-lock.json + npm i prettier@3 prettier-plugin-sql@0.17.0 + - name: Run prettier + run: |- + npx prettier -c '{src,test}/**/*.ts' - - name: Set up Node - uses: actions/setup-node@v1 + docker: + name: Build with docker + runs-on: ubuntu-22.04 + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v5 + name: Checkout Repo + + - uses: docker/setup-buildx-action@v3 + name: Set up Docker Buildx + + - uses: docker/build-push-action@v5 with: - node-version: ${{ matrix.node }} + push: false + tags: pg-meta:test + load: true + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Check Health status + run: | + docker run -d --name pg-meta-test pg-meta:test + state=$(docker inspect -f '{{ .State.Health.Status}}' pg-meta-test) + if [ $state != "starting" ]; then + exit 1 + fi + sleep 10 + state=$(docker inspect -f '{{ .State.Health.Status}}' pg-meta-test) + docker stop pg-meta-test + if [ $state == "healthy" ]; then + exit 0 + else + exit 1 + fi + + - - run: | - npm ci - npm run dev & - sleep 30 - npm test diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 338d6d1c..3630904a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,27 +6,28 @@ on: - master workflow_dispatch: -jobs: - docs: - name: Publish docs / OS ${{ matrix.os }} / Node ${{ matrix.node }} - strategy: - matrix: - os: - - ubuntu-latest - node: - - '12' +permissions: + contents: write + pages: write - runs-on: ${{ matrix.os }} +# Cancel old builds on new commit for same workflow + branch/PR +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +jobs: + docs: + name: Publish docs + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node }} + node-version-file: '.nvmrc' - run: | - npm ci + npm clean-install npm run docs:export - name: Generate Swagger UI @@ -36,7 +37,7 @@ jobs: spec-file: openapi.json - name: Publish - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: docs diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml new file mode 100644 index 00000000..5c56a23b --- /dev/null +++ b/.github/workflows/mirror.yml @@ -0,0 +1,39 @@ +name: Mirror Image + +on: + workflow_dispatch: + inputs: + version: + description: 'Image tag' + required: true + type: string + +permissions: + contents: read + +jobs: + mirror: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.PROD_AWS_ROLE }} + aws-region: us-east-1 + - uses: docker/login-action@v3 + with: + registry: public.ecr.aws + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: akhilerm/tag-push-action@v2.2.0 + with: + src: docker.io/supabase/postgres-meta:${{ inputs.version }} + dst: | + public.ecr.aws/supabase/postgres-meta:${{ inputs.version }} + ghcr.io/supabase/postgres-meta:${{ inputs.version }} diff --git a/.github/workflows/publish-deps.yml b/.github/workflows/publish-deps.yml new file mode 100644 index 00000000..693a3edd --- /dev/null +++ b/.github/workflows/publish-deps.yml @@ -0,0 +1,33 @@ +name: Publish Dependencies + +on: + workflow_dispatch: + +permissions: + contents: read + packages: write + id-token: write + +jobs: + publish: + # Must match glibc verison in node:20 + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v5 + with: + repository: 'pyramation/libpg-query-node' + ref: 'v15' + + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + + - run: npm i + - run: npm run binary:build + + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.PROD_AWS_ROLE }} + aws-region: us-east-1 + + - run: npx node-pre-gyp publish diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c7011b97..71bc8f6c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,142 +7,88 @@ on: workflow_dispatch: jobs: - release: - name: Release / Node ${{ matrix.node }} - strategy: - matrix: - node: ['12'] - - runs-on: ubuntu-latest - + semantic-release: + name: Release + runs-on: ubuntu-24.04 + outputs: + new-release-published: ${{ steps.semantic-release.outputs.new_release_published }} + new-release-version: ${{ steps.semantic-release.outputs.new_release_version }} + permissions: + contents: write + id-token: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - - name: Set up Node - uses: actions/setup-node@v1 + - uses: actions/setup-node@v6 with: - node-version: ${{ matrix.node }} + node-version-file: '.nvmrc' + + - name: Update npm + run: npm install -g npm@latest - run: | - npm ci + npm clean-install npm run build - - name: Get version before - run: echo "VERSION_BEFORE=$(curl -s https://api.github.com/repos/supabase/postgres-meta/releases/latest | jq .name -r)" >> $GITHUB_ENV - - - name: Release on GitHub - run: npx semantic-release + - id: semantic-release + uses: cycjimmy/semantic-release-action@v6 + with: + semantic_version: 25.0.1 # version with latest npm and support for trusted publishing env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Get version after - run: echo "VERSION_AFTER=$(curl -s https://api.github.com/repos/supabase/postgres-meta/releases/latest | jq .name -r)" >> $GITHUB_ENV - - - name: Check version difference - run: | - if [ ${{ env.VERSION_BEFORE }} = ${{ env.VERSION_AFTER }} ]; then - echo 0 > has-new-release - else - echo 1 > has-new-release - fi - - - uses: actions/upload-artifact@v1 - with: - name: has-new-release - path: has-new-release - - upload: - name: Upload / Node ${{ matrix.node }} + docker-hub: + name: Release on Docker Hub needs: - - release - - strategy: - matrix: - node: ['12'] - + - semantic-release + if: needs.semantic-release.outputs.new-release-published == 'true' runs-on: ubuntu-latest - + permissions: + id-token: write # This is required for requesting the JWT from AWS + contents: read + packages: write steps: - - uses: actions/download-artifact@v1 + - id: meta + uses: docker/metadata-action@v5 with: - name: has-new-release - - - name: Check for new release - run: echo "HAS_NEW_RELEASE=$(cat has-new-release/has-new-release)" >> $GITHUB_ENV - - - uses: actions/checkout@v2 - if: env.HAS_NEW_RELEASE == 1 - - - uses: actions/cache@v1 - if: env.HAS_NEW_RELEASE == 1 + images: | + supabase/postgres-meta + public.ecr.aws/supabase/postgres-meta + ghcr.io/supabase/postgres-meta + tags: | + type=raw,value=v${{ needs.semantic-release.outputs.new-release-version }} + + - uses: docker/setup-qemu-action@v3 with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- + platforms: amd64,arm64 + - uses: docker/setup-buildx-action@v3 - - name: Set up Node - if: env.HAS_NEW_RELEASE == 1 - uses: actions/setup-node@v1 + - name: Login to DockerHub + uses: docker/login-action@v3 with: - node-version: ${{ matrix.node }} - - - name: Prepare release - if: env.HAS_NEW_RELEASE == 1 - run: | - npm ci - npm run pkg - tar -czvf postgres-meta-linux.tar.gz -C ./bin postgres-meta-linux - tar -czvf postgres-meta-macos.tar.gz -C ./bin postgres-meta-macos - tar -czvf postgres-meta-windows.tar.gz -C ./bin postgres-meta-win.exe - - - name: Get upload url - if: env.HAS_NEW_RELEASE == 1 - run: echo "UPLOAD_URL=$(curl -s https://api.github.com/repos/supabase/postgres-meta/releases/latest | jq .upload_url -r)" >> $GITHUB_ENV + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - - name: Upload linux release asset - if: env.HAS_NEW_RELEASE == 1 - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v4 with: - upload_url: ${{ env.UPLOAD_URL }} - asset_path: ./postgres-meta-linux.tar.gz - asset_name: postgres-meta-linux.tar.gz - asset_content_type: application/gzip + role-to-assume: ${{ secrets.PROD_AWS_ROLE }} + aws-region: us-east-1 - - name: Upload macos release asset - if: env.HAS_NEW_RELEASE == 1 - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Login to ECR + uses: docker/login-action@v3 with: - upload_url: ${{ env.UPLOAD_URL }} - asset_path: ./postgres-meta-macos.tar.gz - asset_name: postgres-meta-macos.tar.gz - asset_content_type: application/gzip + registry: public.ecr.aws - - name: Upload windows release asset - if: env.HAS_NEW_RELEASE == 1 - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Login to GHCR + uses: docker/login-action@v3 with: - upload_url: ${{ env.UPLOAD_URL }} - asset_path: ./postgres-meta-windows.tar.gz - asset_name: postgres-meta-windows.tar.gz - asset_content_type: application/gzip + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Get version - run: echo "VERSION=$(curl -s https://api.github.com/repos/supabase/postgres-meta/releases/latest | jq .name -r)" >> $GITHUB_ENV - - - name: Upload image to Docker Hub - if: env.HAS_NEW_RELEASE == 1 - uses: docker/build-push-action@v1 + - uses: docker/build-push-action@v6 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - repository: supabase/postgres-meta - tags: latest,${{ env.VERSION }} + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} diff --git a/.github/workflows/validate-python-types.yml b/.github/workflows/validate-python-types.yml new file mode 100644 index 00000000..1e5809b1 --- /dev/null +++ b/.github/workflows/validate-python-types.yml @@ -0,0 +1,71 @@ +name: Validate Python Type Generation + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + validate-python-types: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build project + run: npm run build + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Python dependencies + run: | + pip install pydantic mypy + + - name: Start test database + working-directory: test/db + run: | + docker compose up -d --wait + + - name: Wait for database to be ready + run: | + # Install PostgreSQL client for health check + sudo apt-get update && sudo apt-get install -y postgresql-client + until pg_isready -h localhost -p 5432 -U postgres; do + echo "Waiting for database..." + sleep 1 + done + echo "Database is ready!" + + - name: Generate Python types + id: generate-types + run: | + node --loader ts-node/esm scripts/generate-python-types-test.ts > generated_types.py + echo "Generated Python types (first 30 lines):" + head -30 generated_types.py + + - name: Validate Python types runtime + run: | + python -c "import generated_types; print('βœ“ Generated Python types are valid and can be imported')" + + - name: Validate Python types with mypy + run: | + mypy generated_types.py --strict + + - name: Cleanup + if: always() + working-directory: test/db + run: docker compose down diff --git a/.gitignore b/.gitignore index 8d050113..7a26dfc4 100644 --- a/.gitignore +++ b/.gitignore @@ -73,6 +73,9 @@ typings/ .env .env.test +# sentry cli config +.sentryclirc + # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..85aee5a5 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20 \ No newline at end of file diff --git a/.pkg.config.json b/.pkg.config.json deleted file mode 100644 index fc41ced7..00000000 --- a/.pkg.config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@supabase/postgres-meta", - "bin": { - "postgres-meta": "bin/src/server/app.js" - }, - "pkg": { - "assets": ["bin/**/*.sql"], - "scripts": ["node_modules/pg-format/lib/reserved.js"] - } -} diff --git a/.prettierrc b/.prettierrc.json similarity index 100% rename from .prettierrc rename to .prettierrc.json diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 00000000..09b4ea86 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,13 @@ +{ + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/github", { + "successComment": false, + "failTitle": false + } + ], + "@semantic-release/npm" + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..c6d17b80 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contributing + +### Install deps + +- docker +- `npm install` + +### Start services + +1. Run `docker compose up` in `/test/db` +2. Run the tests: `npm run test:run` +3. Make changes in code (`/src`) and tests (`/test/lib` and `/test/server`) +4. Run the tests again: `npm run test:run` +5. Commit + PR + +### Canary Deployments + +For testing your changes when they impact other things (like type generation and postgrest-js), you can deploy a canary version of postgres-meta: + +1. **Create a Pull Request** targeting the `master` branch +2. **Add the `deploy-canary` label** to your PR +3. **Wait for the canary build** - GitHub Actions will automatically build and push a canary Docker image +4. **Use the canary image** - The bot will comment on your PR with the exact image tag and usage instructions + +The canary image will be tagged as: + +- `supabase/postgres-meta:canary-pr-{PR_NUMBER}-{COMMIT_SHA}` +- `supabase/postgres-meta:canary-pr-{PR_NUMBER}` + +Example usage: + +```bash +docker pull supabase/postgres-meta:canary-pr-123-abc1234 +echo "canary-pr-123-abc1234" > supabase/.temp/pgmeta-version +``` + +**Note:** Only maintainers can add the `deploy-canary` label for security reasons. The canary deployment requires access to production Docker registries. diff --git a/Dockerfile b/Dockerfile index 169e31c3..df79412a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,23 @@ -# docker build -t postgres-meta . -# docker run -t -i -p8080:8080 postgres-meta +FROM node:20 AS build +WORKDIR /usr/src/app +# Do `npm ci` separately so we can cache `node_modules` +# https://nodejs.org/en/docs/guides/nodejs-docker-webapp/ +COPY package.json package-lock.json ./ +RUN npm clean-install +COPY . . +RUN npm run build && npm prune --omit=dev -FROM debian:stable-slim -COPY ./bin/postgres-meta-linux /bin/ +FROM node:20-slim +RUN apt-get update && apt-get install -y \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /usr/src/app +COPY --from=build /usr/src/app/node_modules node_modules +COPY --from=build /usr/src/app/dist dist +COPY package.json ./ ENV PG_META_PORT=8080 -ENTRYPOINT "/bin/postgres-meta-linux" +# `npm run start` does not forward signals to child process +CMD ["node", "dist/server/server.js"] EXPOSE 8080 +# --start-period defaults to 0s, but can't be set to 0s (to be explicit) by now +HEALTHCHECK --interval=5s --timeout=5s --retries=3 CMD node -e "fetch('http://localhost:8080/health').then((r) => {if (r.status !== 200) throw new Error(r.status)})" diff --git a/README.md b/README.md index 6fb78170..dd73028a 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ https://supabase.github.io/postgres-meta/ Schema: - [X] `POST /query` (Execute SQL query) + - [x] `POST /format` (Format SQL query) + - [x] `POST /parse` (Parse SQL query into AST) + - [ ] `POST /explain` (Explain SQL query) - [X] `/columns` - [X] GET (List) - [X] POST (`alter table add column`) @@ -64,12 +67,14 @@ Helpers: - [ ] `/generators` - [ ] GET `/openapi`: Generate Open API - [ ] GET `/typescript`: Generate Typescript types + - [ ] GET `/swift`: Generate Swift types (beta) ## Quickstart Set the following ENV VARS: ```bash +PG_META_HOST="0.0.0.0" PG_META_PORT=8080 PG_META_DB_HOST="postgres" PG_META_DB_NAME="postgres" @@ -80,11 +85,12 @@ PG_META_DB_PASSWORD="postgres" Then run any of the binaries in the releases. + ## FAQs -**Why*?* +**Why?** -This servers as a light-weight connection pooler. It also normalises the Postgres system catalog into a more readable format. While it it a lot of reinventing right now, this server will eventually provide helpers (such as type generators). The server is multi-tenant, so it can support multiple Postgres databases from a single server. +This serves as a light-weight connection pooler. It also normalises the Postgres system catalog into a more readable format. While there is a lot of re-inventing right now, this server will eventually provide helpers (such as type generators). The server is multi-tenant, so it can support multiple Postgres databases from a single server. **What security does this use?** @@ -92,16 +98,21 @@ None. Please don't use this as a standalone server. This should be used behind a ## Developers -1. Start the database using `docker-compose up` -2. Run `npm run dev` -3. Run `npm test` while you work +To start developing, run `npm run dev`. It will set up the database with Docker for you. The server will restart on file change. -## Licence +If you are fixing a bug, you should create a new test case. To test your changes, add the `-u` flag to `vitest` on the `test:run` script, run `npm run test`, and then review the git diff of the snapshots. Depending on your change, you may see `id` fields being changed - this is expected and you are free to commit it, as long as it passes the CI. Don't forget to remove the `-u` flag when committing. -Apache 2.0 +To make changes to the type generation, run `npm run gen:types:` while you have `npm run dev` running, +where `` is one of: + +- `typescript` +- `go` +- `swift` (beta) -## Sponsors +To use your own database connection string instead of the provided test database, run: +`PG_META_DB_URL=postgresql://postgres:postgres@localhost:5432/postgres npm run gen:types:` -We are building the features of Firebase using enterprise-grade, open source products. We support existing communities wherever possible, and if the products don’t exist we build them and open source them ourselves. +## Licence + +Apache 2.0 -[![New Sponsor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase) diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100755 index 96173543..00000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# docker-compose.yml -version: "3" -services: - db: - image: postgres:13 - ports: - - "5432:5432" - volumes: - - ./test/postgres/mnt:/docker-entrypoint-initdb.d/ - environment: - POSTGRES_PASSWORD: postgres diff --git a/package-lock.json b/package-lock.json index 2d2a7ee5..e8fe798a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "@supabase/postgres-meta", "version": "0.0.0-automated", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -9,4672 +9,5358 @@ "version": "0.0.0-automated", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.16.7", - "pg": "^7.0.0", - "pg-format": "^1.0.4" + "@fastify/cors": "^9.0.1", + "@fastify/swagger": "^8.2.1", + "@fastify/type-provider-typebox": "^3.5.0", + "@sentry/node": "^9.12.0", + "@sentry/profiling-node": "^9.12.0", + "@sinclair/typebox": "^0.31.25", + "close-with-grace": "^2.1.0", + "crypto-js": "^4.0.0", + "fastify": "^4.24.3", + "fastify-metrics": "^10.0.0", + "pg": "npm:@supabase/pg@0.0.3", + "pg-connection-string": "^2.7.0", + "pg-format": "^1.0.4", + "pg-protocol": "npm:@supabase/pg-protocol@0.0.2", + "pgsql-parser": "^17.8.2", + "pino": "^9.5.0", + "postgres-array": "^3.0.1", + "prettier": "^3.3.3", + "prettier-plugin-sql": "0.17.1" }, "devDependencies": { - "@types/crypto-js": "^3.1.47", - "@types/node": "^14.0.13", - "@types/pg": "^7.14.3", + "@types/crypto-js": "^4.1.1", + "@types/node": "^20.11.14", + "@types/pg": "^8.11.10", "@types/pg-format": "^1.0.1", - "axios": "^0.21.1", - "cpy-cli": "^3.1.1", - "crypto-js": "^4.0.0", - "esm": "^3.2.25", - "fastify": "^3.14.0", - "fastify-cors": "^5.2.0", - "fastify-swagger": "^4.7.0", - "mocha": "^7.1.2", + "@vitest/coverage-v8": "^3.0.5", + "cpy-cli": "^5.0.0", + "nodemon": "^3.1.7", "npm-run-all": "^4.1.5", - "pino-pretty": "^4.7.1", - "pkg": "^4.4.8", - "prettier": "^2.0.5", - "rimraf": "^3.0.2", - "ts-node-dev": "^1.1.6", - "typescript": "^4.3.2" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.12.13" + "pino-pretty": "^13.1.1", + "rimraf": "^6.0.1", + "ts-node": "^10.9.1", + "typescript": "^5.6.3", + "vitest": "^3.0.5" + }, + "engines": { + "node": ">=20", + "npm": ">=9" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", - "dev": true - }, - "node_modules/@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", - "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", - "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, - "node_modules/@fastify/forwarded": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-1.0.0.tgz", - "integrity": "sha512-VoO+6WD0aRz8bwgJZ8pkkxjq7o/782cQ1j945HWg0obZMgIadYW3Pew0+an+k1QL7IPZHM3db5WF6OP6x4ymMA==", + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=6.9.0" } }, - "node_modules/@fastify/proxy-addr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-3.0.0.tgz", - "integrity": "sha512-ty7wnUd/GeSqKTC2Jozsl5xGbnxUnEFC0On2/zPv/8ixywipQmVZwuWvNGnBoitJ2wixwVqofwXNua8j6Y62lQ==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, - "dependencies": { - "@fastify/forwarded": "^1.0.0", - "ipaddr.js": "^2.0.0" + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@hapi/bourne": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz", - "integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg==", - "dev": true - }, - "node_modules/@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "dev": true, + "license": "MIT", "dependencies": { - "call-me-maybe": "^1.0.1", - "glob-to-regexp": "^0.3.0" + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "dev": true, + "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.3", - "run-parallel": "^1.1.9" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "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==", "dev": true, + "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.3", - "fastq": "^1.6.0" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">= 8" + "node": ">=12" } }, - "node_modules/@sinclair/typebox": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.16.7.tgz", - "integrity": "sha512-d12AkLZJXD30hXBhJSgf33RqGO0NMHIDzsQPYfp6WGoaSuMnSGIUanII2OUbeZFnD/j3Nbl2zifgO2+5tPClCQ==" - }, - "node_modules/@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "node_modules/@types/crypto-js": { - "version": "3.1.47", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-3.1.47.tgz", - "integrity": "sha512-eI6gvpcGHLk3dAuHYnRCAjX+41gMv1nz/VP55wAe5HtmAKDOoPSfr3f6vkMc08ov1S0NsjvUBxDtHHxqQY1LGA==", - "dev": true - }, - "node_modules/@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "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==", "dev": true, + "license": "MIT", "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true - }, - "node_modules/@types/minimist": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", - "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", - "dev": true - }, - "node_modules/@types/node": { - "version": "14.0.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.13.tgz", - "integrity": "sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA==", - "dev": true - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true - }, - "node_modules/@types/pg": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-7.14.3.tgz", - "integrity": "sha512-go5zddQ1FrUQHeBvqPzQ1svKo4KKucSwvqLsvwc/EIuQ9sxDA21b68xc/RwhzAK5pPCnez8NrkYatFIGdJBVvA==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "@types/node": "*", - "@types/pg-types": "*" + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/pg-format": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/pg-format/-/pg-format-1.0.1.tgz", - "integrity": "sha512-TC073gk+fvWGenuE7sDEjmt33HJkNhKJ9uLRgP9O9KQGSUaN1hA7Snbr63EU9/aE1X4Zz0+1BXhSBdZCYFWXog==", - "dev": true - }, - "node_modules/@types/pg-types": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@types/pg-types/-/pg-types-1.11.5.tgz", - "integrity": "sha512-L8ogeT6vDzT1vxlW3KITTCt+BVXXVkLXfZ/XNm6UqbcJgxf+KPO7yjWx7dQQE8RW07KopL10x2gNMs41+IkMGQ==", - "dev": true - }, - "node_modules/@types/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", - "dev": true - }, - "node_modules/@types/strip-json-comments": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", - "dev": true - }, - "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==", - "dev": true - }, - "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==", + "node_modules/@esbuild/android-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "node_modules/@esbuild/android-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "cpu": [ + "arm64" + ], "dev": true, - "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" + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "node_modules/@esbuild/android-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/args": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", - "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", + "node_modules/@esbuild/linux-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "camelcase": "5.0.0", - "chalk": "2.4.2", - "leven": "2.1.0", - "mri": "1.1.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 6.0.0" + "node": ">=18" } }, - "node_modules/args/node_modules/camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "cpu": [ + "ia32" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "cpu": [ + "loong64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "cpu": [ + "mips64el" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "cpu": [ + "ppc64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "cpu": [ + "riscv64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "cpu": [ + "s390x" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "node_modules/@esbuild/linux-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=0.8" + "node": ">=18" } }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "cpu": [ + "arm64" + ], "dev": true, - "bin": { - "atob": "bin/atob.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 4.5.0" + "node": ">=18" } }, - "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==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "cpu": [ + "ia32" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8.0.0" + "node": ">=18" } }, - "node_modules/avvio": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.1.tgz", - "integrity": "sha512-b+gox68dqD6c3S3t+bZBKN6rYbVWdwpN12sHQLFTiacDT2rcq7fm07Ww+IKt/AvAkyCIe1f5ArP1bC/vAlx97A==", + "node_modules/@esbuild/win32-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz", + "integrity": "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==", + "license": "MIT", "dependencies": { - "archy": "^1.0.0", - "debug": "^4.0.0", - "fastq": "^1.6.1", - "queue-microtask": "^1.1.2" + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" } }, - "node_modules/avvio/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, + "node_modules/@fastify/cors": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-9.0.1.tgz", + "integrity": "sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" + "fastify-plugin": "^4.0.0", + "mnemonist": "0.39.6" } }, - "node_modules/avvio/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/@fastify/error": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", + "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==", + "license": "MIT" }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true, - "engines": { - "node": "*" + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", + "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^5.7.0" } }, - "node_modules/aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", - "dev": true + "node_modules/@fastify/merge-json-schemas": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", + "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } }, - "node_modules/axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dev": true, + "node_modules/@fastify/swagger": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-8.15.0.tgz", + "integrity": "sha512-zy+HEEKFqPMS2sFUsQU5X0MHplhKJvWeohBwTCkBAJA/GDYGLGUWQaETEhptiqxK7Hs0fQB9B4MDb3pbwIiCwA==", + "license": "MIT", "dependencies": { - "follow-redirects": "^1.10.0" + "fastify-plugin": "^4.0.0", + "json-schema-resolver": "^2.0.0", + "openapi-types": "^12.0.0", + "rfdc": "^1.3.0", + "yaml": "^2.2.2" } }, - "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/@fastify/type-provider-typebox": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@fastify/type-provider-typebox/-/type-provider-typebox-3.6.0.tgz", + "integrity": "sha512-HTeOLvirfGg0u1KGao3iXn5rZpYNqlrOmyDnXSXAbWVPa+mDQTTBNs/x5uZzOB6vFAqr0Xcf7x1lxOamNSYKjw==", + "license": "MIT", + "peerDependencies": { + "@sinclair/typebox": ">=0.26 <=0.32" + } }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": "20 || >=22" } }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", "dev": true, + "license": "MIT", "dependencies": { - "is-descriptor": "^1.0.0" + "@isaacs/balanced-match": "^4.0.1" }, "engines": { - "node": ">=0.10.0" + "node": "20 || >=22" } }, - "node_modules/base/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", "dependencies": { - "kind-of": "^6.0.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/base/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { - "tweetnacl": "^0.14.3" + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "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==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6.0.0" } }, - "node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6.0.0" } }, - "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/@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==", + "dev": true, + "license": "MIT" }, - "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } }, - "node_modules/buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "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": ">=4" + "node": ">= 8" } }, - "node_modules/byline": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", - "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=", + "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": ">=0.10.0" + "node": ">= 8" } }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "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": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, - "node_modules/call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", - "dev": true - }, - "node_modules/camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">=8.0.0" } }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, + "node_modules/@opentelemetry/api-logs": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", + "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", + "license": "Apache-2.0", "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=8" + "node": ">=14" } }, - "node_modules/camelcase-keys/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", + "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=6" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", + "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "node_modules/@opentelemetry/core/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", + "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@opentelemetry/api-logs": "0.57.2", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" }, "engines": { - "node": ">=4" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-amqplib": { + "version": "0.46.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.46.1.tgz", + "integrity": "sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ==", + "license": "Apache-2.0", "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">= 8.10.0" + "node": ">=14" }, - "optionalDependencies": { - "fsevents": "~2.3.1" + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/chokidar/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-connect": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.43.1.tgz", + "integrity": "sha512-ht7YGWQuV5BopMcw5Q2hXn3I8eG8TH0J/kc/GMcW4CuNTgiP6wCu44BOnucJWL3CmFWaRHI//vWyAhaC8BwePw==", + "license": "Apache-2.0", "dependencies": { - "fill-range": "^7.0.1" + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.38" }, "engines": { - "node": ">=8" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/chokidar/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-dataloader": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.16.1.tgz", + "integrity": "sha512-K/qU4CjnzOpNkkKO4DfCLSQshejRNAJtd4esgigo/50nxCB6XCyi1dhAblUHM9jG5dRm8eu0FB+t87nIo99LYQ==", + "license": "Apache-2.0", "dependencies": { - "to-regex-range": "^5.0.1" + "@opentelemetry/instrumentation": "^0.57.1" }, "engines": { - "node": ">=8" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/chokidar/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, + "node_modules/@opentelemetry/instrumentation-express": { + "version": "0.47.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.47.1.tgz", + "integrity": "sha512-QNXPTWteDclR2B4pDFpz0TNghgB33UMjUt14B+BZPmtH1MwUFAfLHBaP5If0Z5NZC+jaH8oF2glgYjrmhZWmSw==", + "license": "Apache-2.0", "dependencies": { - "is-glob": "^4.0.1" + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">= 6" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/chokidar/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, + "node_modules/@opentelemetry/instrumentation-fastify": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.44.2.tgz", + "integrity": "sha512-arSp97Y4D2NWogoXRb8CzFK3W2ooVdvqRRtQDljFt9uC3zI6OuShgey6CVFC0JxT1iGjkAr1r4PDz23mWrFULQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, "engines": { - "node": ">=0.12.0" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/chokidar/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, + "node_modules/@opentelemetry/instrumentation-fs": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.19.1.tgz", + "integrity": "sha512-6g0FhB3B9UobAR60BGTcXg4IHZ6aaYJzp0Ki5FhnxyAPt8Ns+9SSvgcrnsN2eGmk3RWG5vYycUGOEApycQL24A==", + "license": "Apache-2.0", "dependencies": { - "is-number": "^7.0.0" + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1" }, "engines": { - "node": ">=8.0" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-generic-pool": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.43.1.tgz", + "integrity": "sha512-M6qGYsp1cURtvVLGDrPPZemMFEbuMmCXgQYTReC/IbimV5sGrLBjB+/hANUpRZjX67nGLdKSVLZuQQAiNz+sww==", + "license": "Apache-2.0", "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" + "@opentelemetry/instrumentation": "^0.57.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, + "node_modules/@opentelemetry/instrumentation-graphql": { + "version": "0.47.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.47.1.tgz", + "integrity": "sha512-EGQRWMGqwiuVma8ZLAZnExQ7sBvbOx0N/AE/nlafISPs8S+QtXX+Viy6dcQwVWwYHQPAcuY3bFt3xgoAwb4ZNQ==", + "license": "Apache-2.0", "dependencies": { - "is-descriptor": "^0.1.0" + "@opentelemetry/instrumentation": "^0.57.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "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==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-hapi": { + "version": "0.45.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.45.2.tgz", + "integrity": "sha512-7Ehow/7Wp3aoyCrZwQpU7a2CnoMq0XhIcioFuKjBb0PLYfBfmTsFTUyatlHu0fRxhwcRsSQRTvEhmZu8CppBpQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, "engines": { - "node": ">=6" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.57.2.tgz", + "integrity": "sha512-1Uz5iJ9ZAlFOiPuwYg29Bf7bJJc/GeoeJIFKJYQf67nTVKFe8RHbEtxgkOmK4UGZNHKXcpW4P8cWBYzBn1USpg==", + "license": "Apache-2.0", "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "@opentelemetry/core": "1.30.1", + "@opentelemetry/instrumentation": "0.57.2", + "@opentelemetry/semantic-conventions": "1.28.0", + "forwarded-parse": "2.1.2", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "license": "Apache-2.0", "engines": { - "node": ">=6" + "node": ">=14" } }, - "node_modules/cliui/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-ioredis": { + "version": "0.47.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.47.1.tgz", + "integrity": "sha512-OtFGSN+kgk/aoKgdkKQnBsQFDiG8WdCxu+UrHr0bXScdAmtSzLSraLo7wFIb25RVHfRWvzI5kZomqJYEg/l1iA==", + "license": "Apache-2.0", "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=6" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-kafkajs": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.7.1.tgz", + "integrity": "sha512-OtjaKs8H7oysfErajdYr1yuWSjMAectT7Dwr+axIoZqT9lmEOkD/H/3rgAs8h/NIuEi2imSXD+vL4MZtOuJfqQ==", + "license": "Apache-2.0", "dependencies": { - "ansi-regex": "^4.1.0" + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=6" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, + "node_modules/@opentelemetry/instrumentation-knex": { + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.44.1.tgz", + "integrity": "sha512-U4dQxkNhvPexffjEmGwCq68FuftFK15JgUF05y/HlK3M6W/G2iEaACIfXdSnwVNe9Qh0sPfw8LbOPxrWzGWGMQ==", + "license": "Apache-2.0", "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-koa": { + "version": "0.47.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.47.1.tgz", + "integrity": "sha512-l/c+Z9F86cOiPJUllUCt09v+kICKvT+Vg1vOAJHtHPsJIzurGayucfCMq2acd/A/yxeNWunl9d9eqZ0G+XiI6A==", + "license": "Apache-2.0", "dependencies": { - "color-name": "1.1.3" + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.44.1.tgz", + "integrity": "sha512-5MPkYCvG2yw7WONEjYj5lr5JFehTobW7wX+ZUFy81oF2lr9IPfZk9qO+FTaM0bGEiymwfLwKe6jE15nHn1nmHg==", + "license": "Apache-2.0", "dependencies": { - "delayed-stream": "~1.0.0" + "@opentelemetry/instrumentation": "^0.57.1" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "dependencies": { - "safe-buffer": "5.1.2" + "node": ">=14" }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "dev": true, - "engines": { - "node": ">= 0.6" + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "node_modules/cp-file": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-7.0.0.tgz", - "integrity": "sha512-0Cbj7gyvFVApzpK/uhCtQ/9kE9UnYpxMzaq5nQQC/Dh4iaj5fxp7iEFIullrYwzj8nf0qnsI1Qsx34hAeAebvw==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-mongodb": { + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.52.0.tgz", + "integrity": "sha512-1xmAqOtRUQGR7QfJFfGV/M2kC7wmI2WgZdpru8hJl3S0r4hW0n3OQpEHlSGXJAaNFyvT+ilnwkT+g5L4ljHR6g==", + "license": "Apache-2.0", "dependencies": { - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "nested-error-stacks": "^2.0.0", - "p-event": "^4.1.0" + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/cpy": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/cpy/-/cpy-8.1.1.tgz", - "integrity": "sha512-vqHT+9o67sMwJ5hUd/BAOYeemkU+MuFRsK2c36Xc3eefQpAsp1kAsyDxEDcc5JS1+y9l/XHPrIsVTcyGGmkUUQ==", - "dev": true, - "dependencies": { - "arrify": "^2.0.1", - "cp-file": "^7.0.0", - "globby": "^9.2.0", - "has-glob": "^1.0.0", - "junk": "^3.1.0", - "nested-error-stacks": "^2.1.0", - "p-all": "^2.1.0", - "p-filter": "^2.1.0", - "p-map": "^3.0.0" + "node": ">=14" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/cpy-cli": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/cpy-cli/-/cpy-cli-3.1.1.tgz", - "integrity": "sha512-HCpNdBkQy3rw+uARLuIf0YurqsMXYzBa9ihhSAuxYJcNIrqrSq3BstPfr0cQN38AdMrQiO9Dp4hYy7GtGJsLPg==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-mongoose": { + "version": "0.46.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.46.1.tgz", + "integrity": "sha512-3kINtW1LUTPkiXFRSSBmva1SXzS/72we/jL22N+BnF3DFcoewkdkHPYOIdAAk9gSicJ4d5Ojtt1/HeibEc5OQg==", + "license": "Apache-2.0", "dependencies": { - "cpy": "^8.0.0", - "meow": "^6.1.1" - }, - "bin": { - "cpy": "cli.js" + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/cpy/node_modules/@nodelib/fs.stat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/cpy/node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "dependencies": { - "array-uniq": "^1.0.1" + "node": ">=14" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/cpy/node_modules/dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-mysql": { + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.45.1.tgz", + "integrity": "sha512-TKp4hQ8iKQsY7vnp/j0yJJ4ZsP109Ht6l4RHTj0lNEG1TfgTrIH5vJMbgmoYXWzNHAqBH2e7fncN12p3BP8LFg==", + "license": "Apache-2.0", "dependencies": { - "path-type": "^3.0.0" + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/mysql": "2.15.26" }, "engines": { - "node": ">=4" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/cpy/node_modules/fast-glob": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", - "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-mysql2": { + "version": "0.45.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.45.2.tgz", + "integrity": "sha512-h6Ad60FjCYdJZ5DTz1Lk2VmQsShiViKe0G7sYikb0GHI0NVvApp2XQNRHNjEMz87roFttGPLHOYVPlfy+yVIhQ==", + "license": "Apache-2.0", "dependencies": { - "@mrmlnc/readdir-enhanced": "^2.2.1", - "@nodelib/fs.stat": "^1.1.2", - "glob-parent": "^3.1.0", - "is-glob": "^4.0.0", - "merge2": "^1.2.3", - "micromatch": "^3.1.10" + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@opentelemetry/sql-common": "^0.40.1" }, "engines": { - "node": ">=4.0.0" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/cpy/node_modules/globby": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", - "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-pg": { + "version": "0.51.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.51.1.tgz", + "integrity": "sha512-QxgjSrxyWZc7Vk+qGSfsejPVFL1AgAJdSBMYZdDUbwg730D09ub3PXScB9d04vIqPriZ+0dqzjmQx0yWKiCi2Q==", + "license": "Apache-2.0", "dependencies": { - "@types/glob": "^7.1.1", - "array-union": "^1.0.2", - "dir-glob": "^2.2.2", - "fast-glob": "^2.2.6", - "glob": "^7.1.3", - "ignore": "^4.0.3", - "pify": "^4.0.1", - "slash": "^2.0.0" + "@opentelemetry/core": "^1.26.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@opentelemetry/sql-common": "^0.40.1", + "@types/pg": "8.6.1", + "@types/pg-pool": "2.0.6" }, "engines": { - "node": ">=6" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/cpy/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" + "node_modules/@opentelemetry/instrumentation-pg/node_modules/@types/pg": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", + "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" } }, - "node_modules/cpy/node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-pg/node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", "dependencies": { - "pify": "^3.0.0" + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" }, "engines": { "node": ">=4" } }, - "node_modules/cpy/node_modules/path-type/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, + "node_modules/@opentelemetry/instrumentation-pg/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/cpy/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-pg/node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/cpy/node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-pg/node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "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==", - "dev": true - }, - "node_modules/crypto-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz", - "integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==", - "dev": true - }, - "node_modules/currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, + "node_modules/@opentelemetry/instrumentation-pg/node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", "dependencies": { - "array-find-index": "^1.0.1" + "xtend": "^4.0.0" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, + "node_modules/@opentelemetry/instrumentation-redis-4": { + "version": "0.46.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.46.1.tgz", + "integrity": "sha512-UMqleEoabYMsWoTkqyt9WAzXwZ4BlFZHO40wr3d5ZvtjKCHlD4YXLm+6OLCeIi/HkX7EXvQaz8gtAwkwwSEvcQ==", + "license": "Apache-2.0", "dependencies": { - "assert-plus": "^1.0.0" + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=0.10" - } - }, - "node_modules/dateformat": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.5.1.tgz", - "integrity": "sha512-OD0TZ+B7yP7ZgpJf5K2DIbj3FZvFvxgFUuaqA/V5zTjAtAAXZ1E8bktHxmAGs4x5b7PflqA9LeQ84Og7wYtF7Q==", - "dev": true, - "engines": { - "node": "*" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-tedious": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.18.1.tgz", + "integrity": "sha512-5Cuy/nj0HBaH+ZJ4leuD7RjgvA844aY2WW+B5uLcWtxGjRZl3MNLuxnNg5DYWZNPO+NafSSnra0q49KWAHsKBg==", + "license": "Apache-2.0", "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true, + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/tedious": "^4.0.14" + }, "engines": { - "node": ">=0.10.0" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "dev": true, + "node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.10.1.tgz", + "integrity": "sha512-rkOGikPEyRpMCmNu9AQuV5dtRlDmJp2dK5sw8roVshAGoB6hH/3QjDtRhdwd75SsJwgynWUNRUYe0wAkTo16tQ==", + "license": "Apache-2.0", "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true, - "engines": { - "node": ">=0.10" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" } }, - "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, + "node_modules/@opentelemetry/redis-common": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz", + "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==", + "license": "Apache-2.0", "engines": { - "node": ">=0.10.0" + "node": ">=14" } }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, + "node_modules/@opentelemetry/resources": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", + "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", + "license": "Apache-2.0", "dependencies": { - "object-keys": "^1.0.12" + "@opentelemetry/core": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" }, "engines": { - "node": ">= 0.4" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "node": ">=14" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/define-property/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, + "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "license": "Apache-2.0", "engines": { - "node": ">=0.10.0" + "node": ">=14" } }, - "node_modules/define-property/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", + "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", + "license": "Apache-2.0", "dependencies": { - "kind-of": "^6.0.0" + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/define-property/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "license": "Apache-2.0", "engines": { - "node": ">=0.10.0" + "node": ">=14" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.32.0.tgz", + "integrity": "sha512-s0OpmpQFSfMrmedAn9Lhg4KWJELHCU6uU9dtIJ28N8UGhf9Y55im5X8fEzwhwDwiSqN+ZPSNrDJF7ivf/AuRPQ==", + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=0.4.0" + "node": ">=14" } }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true, + "node_modules/@opentelemetry/sql-common": { + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz", + "integrity": "sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.1.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" } }, - "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true + "node_modules/@pgsql/types": { + "version": "17.6.1", + "resolved": "https://registry.npmjs.org/@pgsql/types/-/types-17.6.1.tgz", + "integrity": "sha512-Hk51+nyOxS7Dy5oySWywyNZxo5HndX1VDXT4ZEBD+p+vvMFM2Vc+sKSuByCiI8banou4edbgdnOC251IOuq7QQ==", + "license": "SEE LICENSE IN LICENSE" }, - "node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, + "license": "MIT", + "optional": true, "engines": { - "node": ">=0.3.1" + "node": ">=14" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, + "node_modules/@prisma/instrumentation": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.5.0.tgz", + "integrity": "sha512-morJDtFRoAp5d/KENEm+K6Y3PQcn5bCvpJ5a9y3V3DNMrNy/ZSn2zulPGj+ld+Xj2UYVoaMJ8DpBX/o6iF6OiA==", + "license": "Apache-2.0", "dependencies": { - "path-type": "^4.0.0" + "@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@opentelemetry/api": "^1.8" } }, - "node_modules/dynamic-dedupe": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", - "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.37.0.tgz", + "integrity": "sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "xtend": "^4.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.37.0.tgz", + "integrity": "sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.37.0.tgz", + "integrity": "sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA==", + "cpu": [ + "arm64" + ], "dev": true, - "engines": { - "node": ">= 0.8" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/encoding-negotiator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/encoding-negotiator/-/encoding-negotiator-2.0.1.tgz", - "integrity": "sha512-GSK7qphNR4iPcejfAlZxKDoz3xMhnspwImK+Af5WhePS9jUpK/Oh7rUdyENWu+9rgDflOCTmAojBsgsvM8neAQ==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.37.0.tgz", + "integrity": "sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ==", + "cpu": [ + "x64" + ], "dev": true, - "engines": { - "node": ">=10.13.0" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.37.0.tgz", + "integrity": "sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "once": "^1.4.0" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.37.0.tgz", + "integrity": "sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "dev": true, - "dependencies": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - }, - "engines": { - "node": ">= 0.4" - } + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.37.0.tgz", + "integrity": "sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.37.0.tgz", + "integrity": "sha512-jUb/kmn/Gd8epbHKEqkRAxq5c2EwRt0DqhSGWjPFxLeFvldFdHQs/n8lQ9x85oAeVb6bHcS8irhTJX2FCOd8Ag==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.37.0.tgz", + "integrity": "sha512-oNrJxcQT9IcbcmKlkF+Yz2tmOxZgG9D9GRq+1OE6XCQwCVwxixYAa38Z8qqPzQvzt1FCfmrHX03E0pWoXm1DqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.37.0.tgz", + "integrity": "sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ==", + "cpu": [ + "arm64" + ], "dev": true, - "engines": { - "node": ">=0.8.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.37.0.tgz", + "integrity": "sha512-yCE0NnutTC/7IGUq/PUHmoeZbIwq3KRh02e9SfFh7Vmc1Z7atuJRYWhRME5fKgT8aS20mwi1RyChA23qSyRGpA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.37.0.tgz", + "integrity": "sha512-NxcICptHk06E2Lh3a4Pu+2PEdZ6ahNHuK7o6Np9zcWkrBMuv21j10SQDJW3C9Yf/A/P7cutWoC/DptNLVsZ0VQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.37.0.tgz", + "integrity": "sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.37.0.tgz", + "integrity": "sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.37.0.tgz", + "integrity": "sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.37.0.tgz", + "integrity": "sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.37.0.tgz", + "integrity": "sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.37.0.tgz", + "integrity": "sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.37.0.tgz", + "integrity": "sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.37.0.tgz", + "integrity": "sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sentry-internal/node-cpu-profiler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/node-cpu-profiler/-/node-cpu-profiler-2.1.0.tgz", + "integrity": "sha512-/gPj8ARZ8Jw8gCQWToCiUyLoOxBDP8wuFNx07mAXegYiDa4NcIvo37ZzDWaTG+wjwa/LvCpHxHff6pejt4KOKg==", + "hasInstallScript": true, + "license": "MIT", "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" + "detect-libc": "^2.0.3", + "node-abi": "^3.73.0" }, "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" + "node": ">=18" } }, - "node_modules/escodegen/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==", - "dev": true, - "optional": true, + "node_modules/@sentry/core": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.12.0.tgz", + "integrity": "sha512-jOqQK/90uzHmsBvkPTj/DAEFvA5poX4ZRyC7LE1zjg4F5jdOp3+M4W3qCy0CkSTu88Zu5VWBoppCU2Bs34XEqg==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", - "dev": true, + "node_modules/@sentry/node": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-9.12.0.tgz", + "integrity": "sha512-NZHneJovlLOdde85vJAIs7vIki36EfJ234d6YXHUE+874sxKMknB/wrzAZi5XS5nqT3kqIXD5KgjgDTjrhAENQ==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1", + "@opentelemetry/core": "^1.30.1", + "@opentelemetry/instrumentation": "^0.57.2", + "@opentelemetry/instrumentation-amqplib": "^0.46.1", + "@opentelemetry/instrumentation-connect": "0.43.1", + "@opentelemetry/instrumentation-dataloader": "0.16.1", + "@opentelemetry/instrumentation-express": "0.47.1", + "@opentelemetry/instrumentation-fastify": "0.44.2", + "@opentelemetry/instrumentation-fs": "0.19.1", + "@opentelemetry/instrumentation-generic-pool": "0.43.1", + "@opentelemetry/instrumentation-graphql": "0.47.1", + "@opentelemetry/instrumentation-hapi": "0.45.2", + "@opentelemetry/instrumentation-http": "0.57.2", + "@opentelemetry/instrumentation-ioredis": "0.47.1", + "@opentelemetry/instrumentation-kafkajs": "0.7.1", + "@opentelemetry/instrumentation-knex": "0.44.1", + "@opentelemetry/instrumentation-koa": "0.47.1", + "@opentelemetry/instrumentation-lru-memoizer": "0.44.1", + "@opentelemetry/instrumentation-mongodb": "0.52.0", + "@opentelemetry/instrumentation-mongoose": "0.46.1", + "@opentelemetry/instrumentation-mysql": "0.45.1", + "@opentelemetry/instrumentation-mysql2": "0.45.2", + "@opentelemetry/instrumentation-pg": "0.51.1", + "@opentelemetry/instrumentation-redis-4": "0.46.1", + "@opentelemetry/instrumentation-tedious": "0.18.1", + "@opentelemetry/instrumentation-undici": "0.10.1", + "@opentelemetry/resources": "^1.30.1", + "@opentelemetry/sdk-trace-base": "^1.30.1", + "@opentelemetry/semantic-conventions": "^1.30.0", + "@prisma/instrumentation": "6.5.0", + "@sentry/core": "9.12.0", + "@sentry/opentelemetry": "9.12.0", + "import-in-the-middle": "^1.13.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-9.12.0.tgz", + "integrity": "sha512-jQfI/UmgDDbcWY439r1Jz0Y4mqNn3a2JwruWfCHWzIqQMOgBzkzcp9lbZMx9iU+x1iZTTp9s80Dy5F9nG4KKMQ==", + "license": "MIT", + "dependencies": { + "@sentry/core": "9.12.0" + }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1", + "@opentelemetry/core": "^1.30.1", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/sdk-trace-base": "^1.30.1", + "@opentelemetry/semantic-conventions": "^1.28.0" } }, - "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==", - "dev": true, + "node_modules/@sentry/profiling-node": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@sentry/profiling-node/-/profiling-node-9.12.0.tgz", + "integrity": "sha512-ZAOmeytFDDfx5nqsImFoQmFhJFDzRxygyApKfK/5Ap42hDa4TKyZTxzHykVxgGhzMlnjxQv7XU7sAbpwZGzsSw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/node-cpu-profiler": "^2.0.0", + "@sentry/core": "9.12.0", + "@sentry/node": "9.12.0" + }, "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "sentry-prune-profiler-binaries": "scripts/prune-profiler-binaries.js" }, "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/@sinclair/typebox": { + "version": "0.31.28", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.31.28.tgz", + "integrity": "sha512-/s55Jujywdw/Jpan+vsy6JZs1z2ZTGxTmbZTPiuSL2wz9mfzA2gN1zzaqmvfi4pq+uOt7Du85fkiwv5ymW84aQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", "dev": true, - "engines": { - "node": ">=4.0" - } + "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==", + "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==", "dev": true, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "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==", "dev": true, - "engines": { - "node": ">= 0.6" - } + "license": "MIT" }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "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==", "dev": true, + "license": "MIT" + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" + "@types/node": "*" } }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, + "license": "MIT" + }, + "node_modules/@types/mysql": { + "version": "2.15.26", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz", + "integrity": "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "@types/node": "*" } }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, + "node_modules/@types/node": { + "version": "20.17.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.28.tgz", + "integrity": "sha512-DHlH/fNL6Mho38jTy7/JT7sn2wnXI+wULR6PV4gy4VHLVvnrV/d3pHAMQHhc4gjdLmK2ZiPoMxzp6B3yRajLSQ==", + "license": "MIT", + "peer": true, "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" + "undici-types": "~6.19.2" } }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, + "node_modules/@types/pg": { + "version": "8.11.11", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.11.tgz", + "integrity": "sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==", + "license": "MIT", "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "node_modules/@types/pg-format": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/pg-format/-/pg-format-1.0.5.tgz", + "integrity": "sha512-i+oEEJEC+1I3XAhgqtVp45Faj8MBbV0Aoq4rHsHD7avgLjyDkaWKObd514g0Q/DOUkdxU0P4CQ0iq2KR4SoJcw==", "dev": true, - "engines": { - "node": ">=6" + "license": "MIT" + }, + "node_modules/@types/pg-pool": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", + "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", + "license": "MIT", + "dependencies": { + "@types/pg": "*" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "node_modules/@types/shimmer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", + "license": "MIT" + }, + "node_modules/@types/tedious": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", + "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "node_modules/@vitest/coverage-v8": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.9.tgz", + "integrity": "sha512-15OACZcBtQ34keIEn19JYTVuMFTlFrClclwWjHo/IRPg/8ELpkgNTl0o7WLP9WO9XGH6+tip9CPYtEOrIDJvBA==", "dev": true, + "license": "MIT", "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "debug": "^4.4.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.0.9", + "vitest": "3.0.9" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, - "node_modules/extend-shallow/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "node_modules/@vitest/expect": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.9.tgz", + "integrity": "sha512-5eCqRItYgIML7NNVgJj6TVCmdzE7ZVgJhruW0ziSQV4V7PvLkDL1bBkBdcTs/VuIz0IxPb5da1IDSqc1TR9eig==", "dev": true, + "license": "MIT", "dependencies": { - "is-plain-object": "^2.0.4" + "@vitest/spy": "3.0.9", + "@vitest/utils": "3.0.9", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "node_modules/@vitest/mocker": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.9.tgz", + "integrity": "sha512-ryERPIBOnvevAkTq+L1lD+DTFBRcjueL9lOUfXsLfwP92h4e+Heb+PjiqS3/OURWPtywfafK0kj++yDFjWUmrA==", "dev": true, + "license": "MIT", "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "@vitest/spy": "3.0.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "node_modules/@vitest/pretty-format": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.9.tgz", + "integrity": "sha512-OW9F8t2J3AwFEwENg3yMyKWweF7oRJlMyHOMIhO5F3n0+cgQAJZBjNgrF8dLwFTEXl5jUqBLXd9QyyKv8zEcmA==", "dev": true, + "license": "MIT", "dependencies": { - "is-descriptor": "^1.0.0" + "tinyrainbow": "^2.0.0" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "node_modules/@vitest/runner": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.9.tgz", + "integrity": "sha512-NX9oUXgF9HPfJSwl8tUZCMP1oGx2+Sf+ru6d05QjzQz4OwWg0psEzwY6VexP2tTHWdOkhKHUIZH+fS6nA7jfOw==", "dev": true, + "license": "MIT", "dependencies": { - "is-extendable": "^0.1.0" + "@vitest/utils": "3.0.9", + "pathe": "^2.0.3" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/extglob/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "node_modules/@vitest/snapshot": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.9.tgz", + "integrity": "sha512-AiLUiuZ0FuA+/8i19mTYd+re5jqjEc2jZbgJ2up0VY0Ddyyxg/uUtBDpIFAy4uzKaQxOW8gMgBdAJJ2ydhu39A==", "dev": true, + "license": "MIT", "dependencies": { - "kind-of": "^6.0.0" + "@vitest/pretty-format": "3.0.9", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/extglob/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "node_modules/@vitest/spy": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.9.tgz", + "integrity": "sha512-/CcK2UDl0aQ2wtkp3YVWldrpLRNCfVcIOFGlVGKO4R5eajsH393Z1yiXLVQ7vWsj26JOEjeZI0x5sm5P4OGUNQ==", "dev": true, + "license": "MIT", "dependencies": { - "kind-of": "^6.0.0" + "tinyspy": "^3.0.2" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/extglob/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "node_modules/@vitest/utils": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.9.tgz", + "integrity": "sha512-ilHM5fHhZ89MCp5aAaM9uhfl1c2JdxVxl3McqsdVyVNN6JffnEen8UMCdRTzOhGXNQGo5GNL9QugHrz727Wnng==", "dev": true, + "license": "MIT", "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "@vitest/pretty-format": "3.0.9", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true, - "engines": [ - "node >=0.6.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==", - "dev": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true + "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/fast-glob": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.2.tgz", - "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=8" + "node": ">=0.4.0" } }, - "node_modules/fast-glob/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "acorn": "^8.11.0" }, "engines": { - "node": ">=8" + "node": ">=0.4.0" } }, - "node_modules/fast-glob/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">=8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "ajv": "^8.0.0" }, - "engines": { - "node": ">= 6" + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/fast-glob/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==", + "node_modules/ajv/node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.12.0" + "node": ">=8" } }, - "node_modules/fast-glob/node_modules/micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/fast-glob/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==", + "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": { - "is-number": "^7.0.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=8.0" + "node": ">= 8" } }, - "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 + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" }, - "node_modules/fast-json-stringify": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.5.2.tgz", - "integrity": "sha512-H0/Wq7jj/i96DycCySdh+Jl0MubqaScM51GJkdJmjzwyiNpxAgQ9deYqk2H8SNOfwPj21RtTXMkMV5GosUWKeQ==", + "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==", + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, + "license": "MIT", "dependencies": { - "ajv": "^6.11.0", - "deepmerge": "^4.2.2", - "rfdc": "^1.2.0", - "string-similarity": "^4.0.1" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { - "node": ">= 10.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/fast-redact": { + "node_modules/arrify": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.0.0.tgz", - "integrity": "sha512-a/S/Hp6aoIjx7EmugtzLqXmcNsyFszqbt6qQ99BdG61QjBZF6shNis0BYR6TsZOQ1twYc0FN2Xdhwwbv6+KD0w==", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", + "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", - "dev": true - }, - "node_modules/fastify": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.14.0.tgz", - "integrity": "sha512-a6W2iVPJMOaULqCykJ5nFRtnoknqt9K3b6rqAQcGjT/O2Hy+vvo+9/+cL2907KN0iF/91Ke+XQluKrVNF6+Z7w==", - "dev": true, - "dependencies": { - "@fastify/proxy-addr": "^3.0.0", - "abstract-logging": "^2.0.0", - "ajv": "^6.12.2", - "avvio": "^7.1.2", - "fast-json-stringify": "^2.5.0", - "fastify-error": "^0.3.0", - "fastify-warning": "^0.2.0", - "find-my-way": "^4.0.0", - "flatstr": "^1.0.12", - "light-my-request": "^4.2.0", - "pino": "^6.2.1", - "readable-stream": "^3.4.0", - "rfdc": "^1.1.4", - "secure-json-parse": "^2.0.0", - "semver": "^7.3.2", - "tiny-lru": "^7.0.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": ">=10.16.0" + "node": ">=12" } }, - "node_modules/fastify-cors": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-5.2.0.tgz", - "integrity": "sha512-Lde71qT23M3Ip3pmny3uN6q6lQ4J5J0/YWoqe2stL4sMT99R5LEtTJ2La2zijFOR5OFhxvxqGgR7BIc2x3amPg==", + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "dev": true, - "dependencies": { - "fastify-plugin": "^3.0.0", - "vary": "^1.1.2" + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "node_modules/fastify-error": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.0.tgz", - "integrity": "sha512-Jm2LMTB5rsJqlS1+cmgqqM9tTs0UrlgYR7TvDT3ZgXsUI5ib1NjQlqZHf+tDK5tVPdFGwyq02wAoJtyYIRSiFA==", - "dev": true - }, - "node_modules/fastify-plugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz", - "integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w==", - "dev": true - }, - "node_modules/fastify-static": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.2.2.tgz", - "integrity": "sha512-C631EuGdMlUzFXuuP4SqXBoQEiit9S0uYRd97cF2mFhgStvZvQKIjtzUk/GMQu6EfEdm0ddj3UAc0C6dVeNyKA==", - "dev": true, - "dependencies": { - "content-disposition": "^0.5.3", - "encoding-negotiator": "^2.0.1", - "fastify-plugin": "^3.0.0", - "glob": "^7.1.4", - "readable-stream": "^3.4.0", - "send": "^0.17.1" + "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/fastify-static/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "possible-typed-array-names": "^1.0.0" }, "engines": { - "node": "*" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fastify-static/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, + "node_modules/avvio": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.4.0.tgz", + "integrity": "sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==", + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "@fastify/error": "^3.3.0", + "fastq": "^1.17.1" } }, - "node_modules/fastify-swagger": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/fastify-swagger/-/fastify-swagger-4.7.0.tgz", - "integrity": "sha512-IgmHuA8BGpBrvLninqDBQdlJEiB3yEg3XYYmqIcqQqWmU7KYNTQokWNTKG8nBPjj/P2wyTJ0fNxm/K2Fljq/hw==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "dependencies": { - "fastify-plugin": "^3.0.0", - "fastify-static": "^4.0.0", - "js-yaml": "^4.0.0", - "json-schema-resolver": "^1.2.0", - "openapi-types": "^8.0.0" - } + "license": "MIT" }, - "node_modules/fastify-swagger/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 + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } }, - "node_modules/fastify-swagger/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==", + "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, - "dependencies": { - "argparse": "^2.0.1" + "license": "MIT", + "engines": { + "node": ">=8" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fastify-warning": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", - "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==", - "dev": true + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", + "license": "MIT" }, - "node_modules/fastify/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/fastify/node_modules/semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "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": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "fill-range": "^7.1.1" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/fastq": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", - "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, - "dependencies": { - "reusify": "^1.0.4" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, + "license": "MIT", "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, + "license": "MIT", "dependencies": { - "is-extendable": "^0.1.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/find-my-way": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.0.0.tgz", - "integrity": "sha512-IrICzn/Xm5r5A3RCB8rGLNe+dvzZl+SiUugTQOUMLJciP2qiSu2hw9JtVEYV3VruAYzSWjo/MLH9CFKtVdlWhQ==", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, + "license": "MIT", "dependencies": { - "fast-decode-uri-component": "^1.0.1", - "fast-deep-equal": "^3.1.3", - "safe-regex2": "^2.0.0", - "semver-store": "^0.3.0" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/find-my-way/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==", - "dev": true - }, - "node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", "dev": true, + "license": "MIT", "dependencies": { - "locate-path": "^3.0.0" + "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": ">=6" + "node": ">=12" } }, - "node_modules/flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { - "is-buffer": "~2.0.3" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat/node_modules/is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", - "dev": true, "engines": { "node": ">=4" } }, - "node_modules/flatstr": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", - "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz", - "integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==", + "node_modules/chalk/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=4" } }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "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": "*" + "node": ">= 16" } }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "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": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "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": ">= 0.12" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" + "node": ">= 8.10.0" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true, - "engines": { - "node": ">= 0.6" - } + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "license": "MIT" }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } + "node_modules/close-with-grace": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/close-with-grace/-/close-with-grace-2.2.0.tgz", + "integrity": "sha512-OdcFxnxTm/AMLPHA4Aq3J1BLpkojXP7I4G5QBQLN5TT55ED/rk04rAoDbtfNnfZ988kGXPxh1bdRLeIU9bz/lA==", + "license": "MIT" }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, + "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" + "color-name": "1.1.3" } }, - "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/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } + "license": "MIT" }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" }, - "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==", + "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, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } + "license": "MIT" }, - "node_modules/get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "node_modules/cp-file": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-10.0.0.tgz", + "integrity": "sha512-vy2Vi1r2epK5WqxOLnskeKeZkdZvTKfFZQCplE3XWsP+SUJyd5XAUFC9lFgTjjXJF2GMne/UML14iEmkAaDfFg==", "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.10", + "nested-error-stacks": "^2.1.1", + "p-event": "^5.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "node_modules/cpy": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cpy/-/cpy-10.1.0.tgz", + "integrity": "sha512-VC2Gs20JcTyeQob6UViBLnyP0bYHkBh6EiKzot9vi2DmeGlFT9Wd7VG3NBrkNx/jYvFBeyDOMMHdHQhbtKLgHQ==", "dev": true, + "license": "MIT", "dependencies": { - "assert-plus": "^1.0.0" + "arrify": "^3.0.0", + "cp-file": "^10.0.0", + "globby": "^13.1.4", + "junk": "^4.0.1", + "micromatch": "^4.0.5", + "nested-error-stacks": "^2.1.1", + "p-filter": "^3.0.0", + "p-map": "^6.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "node_modules/cpy-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cpy-cli/-/cpy-cli-5.0.0.tgz", + "integrity": "sha512-fb+DZYbL9KHc0BC4NYqGRrDIJZPXUmjjtqdw4XRRg8iV8dIfghUX/WiL+q4/B/KFTy3sK6jsbUhBaz0/Hxg7IQ==", "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "cpy": "^10.1.0", + "meow": "^12.0.1" + }, + "bin": { + "cpy": "cli.js" }, "engines": { - "node": "*" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "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==", "dev": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } + "license": "MIT" }, - "node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, + "license": "MIT", "dependencies": { - "is-extglob": "^2.1.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" }, "engines": { - "node": ">=0.10.0" + "node": ">=4.8" } }, - "node_modules/glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", - "dev": true - }, - "node_modules/globby": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz", - "integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==", + "node_modules/cross-spawn/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" + "license": "ISC", + "bin": { + "semver": "bin/semver" } }, - "node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, "engines": { - "node": ">=4.x" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" } }, - "node_modules/har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, + "license": "MIT", "dependencies": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": "*" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1" + "ms": "^2.1.3" }, "engines": { - "node": ">= 0.4.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "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": ">=4" + "node": ">=6" } }, - "node_modules/has-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-glob/-/has-glob-1.0.0.tgz", - "integrity": "sha1-mqqe7b/7G6OZCnsAEPtnjuAIEgc=", + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, + "license": "MIT", "dependencies": { - "is-glob": "^3.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-glob/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, + "license": "MIT", "dependencies": { - "is-extglob": "^2.1.0" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": ">=0.3.1" } }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, + "license": "MIT", "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "path-type": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "node_modules/discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, + "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">=0.10.0" + "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==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, - "bin": { - "he": "bin/he" - } + "license": "MIT" }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" }, - "node_modules/http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, + "license": "MIT", "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" + "once": "^1.4.0" } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" } }, - "node_modules/ignore": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "node_modules/es-abstract": { + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, "engines": { - "node": ">= 4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true, + "license": "MIT" }, - "node_modules/into-stream": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-5.1.1.tgz", - "integrity": "sha512-krrAJ7McQxGGmvaYbB7Q1mcA+cRwg9Ij2RfWIeVesNBgVDZmzY/Fa4IpZUT3bmdRzMzdf/mzltCG2Dq99IZGBA==", + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, + "license": "MIT", "dependencies": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/ipaddr.js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.0.tgz", - "integrity": "sha512-S54H9mIj0rbxRIyrDMEuuER86LdlgUg9FSeZ8duQb6CUG2iRrA36MYVQBSprTF/ZeAwvyQ5mDGuNvIPM0BIl3w==", - "dev": true, - "engines": { - "node": ">= 10" + "node": ">= 0.4" } }, - "node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, + "license": "MIT", "dependencies": { - "kind-of": "^3.0.2" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, + "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "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==", + "node_modules/esbuild": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" } }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=0.8.0" } }, - "node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" + "@types/estree": "^1.0.0" } }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "node_modules/expect-type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.0.tgz", + "integrity": "sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==", "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, + "license": "Apache-2.0", "engines": { - "node": ">=0.10.0" + "node": ">=12.0.0" } }, - "node_modules/is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "node_modules/fast-content-type-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", + "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==", + "license": "MIT" + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", "dev": true, - "engines": { - "node": ">= 0.4" - } + "license": "MIT" + }, + "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/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, + "license": "MIT", "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.6.0" } }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node_modules/fast-json-stringify": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz", + "integrity": "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==", + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.1.0", + "ajv": "^8.10.0", + "ajv-formats": "^3.0.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.1.0", + "json-schema-ref-resolver": "^1.0.1", + "rfdc": "^1.2.0" } }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node_modules/fast-json-stringify/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/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" + "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/is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true, - "engines": { - "node": ">=4" - } + "license": "MIT" }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, + "node_modules/fast-uri": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", + "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", + "license": "MIT" + }, + "node_modules/fastify": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.29.1.tgz", + "integrity": "sha512-m2kMNHIG92tSNWv+Z3UeTR9AWLLuo7KctC7mlFPtMEVrfjIhmQhkQnT9v15qA/BfVq3vvj134Y0jl9SBje3jXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@fastify/ajv-compiler": "^3.5.0", + "@fastify/error": "^3.4.0", + "@fastify/fast-json-stringify-compiler": "^4.3.0", + "abstract-logging": "^2.0.1", + "avvio": "^8.3.0", + "fast-content-type-parse": "^1.1.0", + "fast-json-stringify": "^5.8.0", + "find-my-way": "^8.0.0", + "light-my-request": "^5.11.0", + "pino": "^9.0.0", + "process-warning": "^3.0.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.0", + "secure-json-parse": "^2.7.0", + "semver": "^7.5.4", + "toad-cache": "^3.3.0" + } + }, + "node_modules/fastify-metrics": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/fastify-metrics/-/fastify-metrics-10.6.0.tgz", + "integrity": "sha512-QIPncCnwBOEObMn+VaRhsBC1ox8qEsaiYF2sV/A1UbXj7ic70W8/HNn/hlEC2W8JQbBeZMx++o1um2fPfhsFDQ==", + "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "fastify-plugin": "^4.3.0", + "prom-client": "^14.2.0" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "fastify": ">=4" } }, - "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, + "node_modules/fastify-plugin": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.5.1.tgz", + "integrity": "sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" + "reusify": "^1.0.4" } }, - "node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "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": { - "is-buffer": "^1.1.5" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true, + "node_modules/find-my-way": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.2.tgz", + "integrity": "sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^3.1.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=14" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, + "license": "MIT", "dependencies": { - "isobject": "^3.0.1" + "is-callable": "^1.2.7" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", "dependencies": { - "has": "^1.0.3" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "node_modules/foreground-child/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.1" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">= 8" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "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==", + "node_modules/foreground-child/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==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/isexe": { + "node_modules/foreground-child/node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "node_modules/jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", + "node_modules/foreground-child/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==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.6.0" + "node": ">=8" } }, - "node_modules/joycon": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-2.2.5.tgz", - "integrity": "sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ==", + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=6" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "node_modules/foreground-child/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "isexe": "^2.0.0" }, "bin": { - "js-yaml": "bin/js-yaml.js" + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "node_modules/json-schema-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-1.2.2.tgz", - "integrity": "sha512-sW4b4BDJzYiKpJind7l1JtH3P1yn43vCv3w51YR2Ixse5rXr006TL10gM0Ek54pET6vxwiWq5RQuIMgmH9YrrQ==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "rfdc": "^1.1.4", - "uri-js": "^4.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/json-schema-resolver/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 0.6" } }, - "node_modules/json-schema-resolver/node_modules/ms": { + "node_modules/forwarded-parse": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": 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==", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.6" - } + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", + "license": "MIT" }, - "node_modules/jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "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, - "engines": [ - "node >=0.6.0" + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" ], - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/junk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", - "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", - "dev": true, - "engines": { - "node": ">=8" + "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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, - "engines": { - "node": ">=0.10.0" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/light-my-request": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.4.1.tgz", - "integrity": "sha512-FDNRF2mYjthIRWE7O8d/X7AzDx4otQHl4/QXbu3Q/FRwBFcgb+ZoDaUd5HwN53uQXLAiw76osN+Va0NEaOW6rQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.2", - "cookie": "^0.4.0", - "fastify-warning": "^0.2.0", - "readable-stream": "^3.6.0", - "set-cookie-parser": "^2.4.1" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/light-my-request/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" } }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true + "node_modules/get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, + "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=6" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "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": { - "chalk": "^2.4.2" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "node_modules/glob/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", "dev": true, + "license": "MIT", "dependencies": { - "semver": "^6.0.0" + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", - "dev": true + "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==", + "dev": true, + "license": "ISC" }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/map-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", - "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", + "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==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, + "license": "MIT", "dependencies": { - "object-visit": "^1.0.0" + "es-define-property": "^1.0.0" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", - "dev": true, - "engines": { - "node": ">= 0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/meow": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz", - "integrity": "sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==", + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, + "license": "MIT", "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "^4.0.2", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" + "dunder-proto": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/meow/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/meow/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==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "license": "MIT", "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=6" - } - }, - "node_modules/merge2": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", - "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", - "dev": true, - "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } + "license": "MIT" }, - "node_modules/mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true, - "engines": { - "node": ">= 0.6" - } + "license": "ISC" }, - "node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "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==", "dev": true, - "dependencies": { - "mime-db": "1.44.0" - }, + "license": "MIT" + }, + "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": ">= 0.6" + "node": ">= 4" } }, - "node_modules/min-indent": { + "node_modules/ignore-by-default": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true, - "engines": { - "node": ">=4" + "license": "ISC" + }, + "node_modules/import-in-the-middle": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.13.1.tgz", + "integrity": "sha512-k2V9wNm9B+ysuelDTHjI9d5KPc4l8zAZTGqj+pcynvWkypZd857ryzN8jNC7Pg2YZXNMJcHRPpaDyCBbNyVRpA==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" } }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { - "node": "*" + "node": ">= 0.4" } }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, + "license": "MIT", "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimist-options/node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, + "license": "MIT", "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "has-bigints": "^1.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "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": { - "is-plain-object": "^2.0.4" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, + "license": "MIT", "dependencies": { - "minimist": "^1.2.5" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz", - "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==", - "dev": true, - "dependencies": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "chokidar": "3.3.0", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "3.0.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.5", - "ms": "2.1.1", - "node-environment-flags": "1.0.6", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha/node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" }, "engines": { - "node": ">= 8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha/node_modules/binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "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, - "dependencies": { - "fill-range": "^7.0.1" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/mocha/node_modules/chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, + "license": "MIT", "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" + "call-bound": "^1.0.3" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 0.4" }, - "optionalDependencies": { - "fsevents": "~2.1.1" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "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==", "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/mocha/node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha/node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "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-glob": "^4.0.1" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">= 6" + "node": ">=0.10.0" } }, - "node_modules/mocha/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==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha/node_modules/is-number": { + "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/mocha/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "node_modules/mocha/node_modules/readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, + "license": "MIT", "dependencies": { - "picomatch": "^2.0.4" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">= 8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha/node_modules/supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha/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==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mri": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", - "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/multistream": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/multistream/-/multistream-2.1.1.tgz", - "integrity": "sha512-xasv76hl6nr1dEy3lPvy7Ej7K/Lx3O/FCvwge8PeVJpciPPoNCbaANcNiBug3IpdvTveZUcAV0DJzdnUDMesNQ==", + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.5" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, + "license": "MIT", "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/nested-error-stacks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", - "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", - "dev": true - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, + "license": "MIT", "dependencies": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-environment-flags/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, - "bin": { - "semver": "bin/semver" + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, + "license": "MIT", "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" - }, - "bin": { - "npm-run-all": "bin/npm-run-all/index.js", - "run-p": "bin/run-p/index.js", - "run-s": "bin/run-s/index.js" - }, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "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==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">= 4" + "node": ">=8" } }, - "node_modules/npm-run-all/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "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==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4.8" + "node": ">=10" } }, - "node_modules/npm-run-all/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, - "bin": { - "semver": "bin/semver" + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/object-assign": { + "node_modules/jackspeak": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, "engines": { - "node": ">=0.10.0" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-ref-resolver": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz", + "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==", + "license": "MIT", "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" + "fast-deep-equal": "^3.1.3" } }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, + "node_modules/json-schema-resolver": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-2.0.0.tgz", + "integrity": "sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==", + "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "debug": "^4.1.1", + "rfdc": "^1.1.4", + "uri-js": "^4.2.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/Eomm/json-schema-resolver?sponsor=1" } }, - "node_modules/object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true + "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/jsox": { + "version": "1.2.121", + "resolved": "https://registry.npmjs.org/jsox/-/jsox-1.2.121.tgz", + "integrity": "sha512-9Ag50tKhpTwS6r5wh3MJSAvpSof0UBr39Pto8OnzFT32Z/pAbxAsKHzyvsyMEHVslELvHyO/4/jaQELHk8wDcw==", + "license": "MIT", + "bin": { + "jsox": "lib/cli.js" + } }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "node_modules/junk": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", + "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, + "node_modules/libpg-query": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/libpg-query/-/libpg-query-17.6.0.tgz", + "integrity": "sha512-r4zOTcLTGYS5PlLQAicJ6Yi/tvZFag42YUuNEO8pi8bwt/ZZ4kj514J4QV5bOx0mZzPLF6agbfNXQVxGgmHR8g==", + "license": "LICENSE IN LICENSE", "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" + "@pgsql/types": "^17.6.0" } }, - "node_modules/object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, + "node_modules/light-my-request": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.14.0.tgz", + "integrity": "sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==", + "license": "BSD-3-Clause", "dependencies": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - }, - "engines": { - "node": ">= 0.4" + "cookie": "^0.7.0", + "process-warning": "^3.0.0", + "set-cookie-parser": "^2.4.1" } }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", "dev": true, + "license": "MIT", "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=4" } }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, + "license": "MIT", "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, + "license": "MIT", "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "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==", "dev": true, + "license": "MIT", "dependencies": { - "wrappy": "1" + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/openapi-types": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-8.0.0.tgz", - "integrity": "sha512-dcHYyCDOAy4QQTrur5Sn1L3lPVspB7rd04Rw/Q7AsMvfV797IiWgmKziFCbq8VhnBoREU/SPPSBDxtK9Biwa1g==", - "dev": true + "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==", + "dev": true, + "license": "ISC" }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">= 0.4" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">= 0.10.0" } }, - "node_modules/p-all": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-all/-/p-all-2.1.0.tgz", - "integrity": "sha512-HbZxz5FONzz/z2gJfk6bFca0BCiSRF8jU3yCsWOen/vR6lZjfPOu/e7L3uFzTW1i0H8TlC3vqQstEJPQL4/uLA==", + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", "dev": true, - "dependencies": { - "p-map": "^2.0.0" - }, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-all/node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "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": ">=6" + "node": ">= 8" } }, - "node_modules/p-event": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", - "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "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": { - "p-timeout": "^3.1.0" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=8" + "node": ">=8.6" } }, - "node_modules/p-filter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", - "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "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": { - "p-map": "^2.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/p-filter/node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, - "engines": { - "node": ">=6" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=4" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/p-is-promise": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", - "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", - "dev": true, + "node_modules/mnemonist": { + "version": "0.39.6", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.6.tgz", + "integrity": "sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==", + "license": "MIT", + "dependencies": { + "obliterator": "^2.0.1" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==", + "license": "MIT" + }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", + "license": "BSD-3-Clause" + }, + "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/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, + "node_modules/nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", + "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" }, - "engines": { - "node": ">=6" + "bin": { + "nearley-railroad": "bin/nearley-railroad.js", + "nearley-test": "bin/nearley-test.js", + "nearley-unparse": "bin/nearley-unparse.js", + "nearleyc": "bin/nearleyc.js" + }, + "funding": { + "type": "individual", + "url": "https://nearley.js.org/#give-to-nearley" } }, - "node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "node_modules/nested-error-stacks": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", + "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true, + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.74.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", + "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", + "license": "MIT", "dependencies": { - "p-limit": "^2.0.0" + "semver": "^7.3.5" }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "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==", - "dev": true, + "node_modules/node-sql-parser": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/node-sql-parser/-/node-sql-parser-4.18.0.tgz", + "integrity": "sha512-2YEOR5qlI1zUFbGMLKNfsrR5JUvFg9LxIRVE+xJe962pfVLH0rnItqLzv96XVs1Y1UIR8FxsXAuvX/lYAWZ2BQ==", + "license": "Apache-2.0", "dependencies": { - "aggregate-error": "^3.0.0" + "big-integer": "^1.6.48" }, "engines": { "node": ">=8" } }, - "node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", "dev": true, + "license": "MIT", "dependencies": { - "p-finally": "^1.0.0" + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" } }, - "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==", + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" - }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "has-flag": "^3.0.0" }, "engines": { "node": ">=4" } }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, - "engines": { - "node": ">=0.10.0" + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, - "engines": { - "node": ">=4" + "license": "ISC", + "bin": { + "semver": "bin/semver" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "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/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, "engines": { - "node": ">=4" + "node": ">= 4" } }, - "node_modules/path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "node_modules/pg": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/pg/-/pg-7.18.2.tgz", - "integrity": "sha512-Mvt0dGYMwvEADNKy5PMQGlzPudKcKKzJds/VbOeZJpb6f/pI3mmoXX0JksPgI3l3JPP/2Apq7F36O63J7mgveA==", - "dependencies": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "0.1.3", - "pg-packet-stream": "^1.1.0", - "pg-pool": "^2.0.10", - "pg-types": "^2.1.0", - "pgpass": "1.x", - "semver": "4.3.2" + "node": ">= 0.4" }, - "engines": { - "node": ">= 4.5.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pg-connection-string": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", - "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" - }, - "node_modules/pg-format": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz", - "integrity": "sha1-J3NCNsKtP05QZJFaWTNOIAQKgo4=", + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">= 0.4" } }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, "engines": { - "node": ">=4.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pg-packet-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pg-packet-stream/-/pg-packet-stream-1.1.0.tgz", - "integrity": "sha512-kRBH0tDIW/8lfnnOyTwKD23ygJ/kexQVXZs7gEyBljw4FYqimZFxnMMx50ndZ8In77QgfGuItS5LLclC2TtjYg==" + "node_modules/obliterator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz", + "integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==", + "license": "MIT" }, - "node_modules/pg-pool": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.10.tgz", - "integrity": "sha512-qdwzY92bHf3nwzIUcj+zJ0Qo5lpG/YxchahxIN8+ZVmXqkahKXsnl2aiJPHLYN9o5mB/leG+Xh6XKxtP7e0sjg==" + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, + "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": ">=4" + "node": ">=14.0.0" } }, - "node_modules/pgpass": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", - "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", "dependencies": { - "split": "^1.0.0" + "wrappy": "1" } }, - "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "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/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, "engines": { - "node": ">=8.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "node_modules/p-event": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-5.0.1.tgz", + "integrity": "sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==", "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" + "license": "MIT", + "dependencies": { + "p-timeout": "^5.0.2" }, "engines": { - "node": ">=0.10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pify": { + "node_modules/p-filter": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-3.0.0.tgz", + "integrity": "sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg==", "dev": true, + "license": "MIT", + "dependencies": { + "p-map": "^5.1.0" + }, "engines": { - "node": ">=4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "node_modules/p-filter/node_modules/aggregate-error": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", + "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^4.0.0", + "indent-string": "^5.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "node_modules/p-filter/node_modules/clean-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", + "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", "dev": true, + "license": "MIT", "dependencies": { - "pinkie": "^2.0.0" + "escape-string-regexp": "5.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pino": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/pino/-/pino-6.11.2.tgz", - "integrity": "sha512-bmzxwbrIPxQUlAuMkF4PWVErUGERU4z37HazlhflKFg08crsNE3fACGN6gPwg5xtKOK47Ux5cZm8YCuLV4wWJg==", + "node_modules/p-filter/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, - "dependencies": { - "fast-redact": "^3.0.0", - "fast-safe-stringify": "^2.0.7", - "flatstr": "^1.0.12", - "pino-std-serializers": "^3.1.0", - "quick-format-unescaped": "4.0.1", - "sonic-boom": "^1.0.2" + "license": "MIT", + "engines": { + "node": ">=12" }, - "bin": { - "pino": "bin.js" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pino-pretty": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-4.7.1.tgz", - "integrity": "sha512-ILE5YBpur88FlZ0cr1BNqVjgG9fOoK+md3peqmcs7AC6oq7SNiaJioIcrykMxfNsuygMYjUJtvAcARRE9aRc9w==", - "dev": true, - "dependencies": { - "@hapi/bourne": "^2.0.0", - "args": "^5.0.1", - "chalk": "^4.0.0", - "dateformat": "^4.5.1", - "fast-safe-stringify": "^2.0.7", - "jmespath": "^0.15.0", - "joycon": "^2.2.5", - "pump": "^3.0.0", - "readable-stream": "^3.6.0", - "split2": "^3.1.1", - "strip-json-comments": "^3.1.1" + "node_modules/p-filter/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" }, - "bin": { - "pino-pretty": "bin.js" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pino-pretty/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==", + "node_modules/p-filter/node_modules/p-map": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz", + "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "aggregate-error": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pino-pretty/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "node_modules/p-map": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-6.0.0.tgz", + "integrity": "sha512-T8BatKGY+k5rU+Q/GTYgrEf2r4xRMevAN5mtXc2aPc4rS1j3s+vWTaO2Wag94neXuCAUAs8cxBL9EeB5EA6diw==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pino-pretty/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==", + "node_modules/p-timeout": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", + "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, + "license": "MIT", "engines": { - "node": ">=7.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pino-pretty/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/pino-pretty/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==", + "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==", "dev": true, - "engines": { - "node": ">=8" - } + "license": "BlueOak-1.0.0" }, - "node_modules/pino-pretty/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" }, "engines": { - "node": ">= 6" + "node": ">=4" } }, - "node_modules/pino-pretty/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==", + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/pino-pretty/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==", + "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==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "has-flag": "^4.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/pino-std-serializers": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", - "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==", - "dev": true - }, - "node_modules/pkg": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/pkg/-/pkg-4.4.8.tgz", - "integrity": "sha512-Fqqv0iaX48U3CFZxd6Dq6JKe7BrAWbgRAqMJkz/m8W3H5cqJ6suvsUWe5AJPRlN/AhbBYXBJ0XG9QlYPTXcVFA==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.9.4", - "@babel/runtime": "^7.9.2", - "chalk": "^3.0.0", - "escodegen": "^1.14.1", - "fs-extra": "^8.1.0", - "globby": "^11.0.0", - "into-stream": "^5.1.1", - "minimist": "^1.2.5", - "multistream": "^2.1.1", - "pkg-fetch": "^2.6.7", - "progress": "^2.0.3", - "resolve": "^1.15.1", - "stream-meter": "^1.0.4" + "node": "20 || >=22" }, - "bin": { - "pkg": "lib-es5/bin.js" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/pkg-fetch": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-2.6.8.tgz", - "integrity": "sha512-CFG7jOeVD38lltLGA7xCJxYsD//GKLjl1P9tc/n9By2a4WEHQjfkBMrYdMS8WOHVP+r9L20fsZNbaKcubDAiQg==", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", "dev": true, - "dependencies": { - "@babel/runtime": "^7.9.2", - "byline": "^5.0.0", - "chalk": "^3.0.0", - "expand-template": "^2.0.3", - "fs-extra": "^8.1.0", - "minimist": "^1.2.5", - "progress": "^2.0.3", - "request": "^2.88.0", - "request-progress": "^3.0.0", - "semver": "^6.3.0", - "unique-temp-dir": "^1.0.0" - }, - "bin": { - "pkg-fetch": "lib-es5/bin.js" + "license": "ISC", + "engines": { + "node": "20 || >=22" } }, - "node_modules/pkg-fetch/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/pkg-fetch/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT" + }, + "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": ">=8" + "node": ">= 14.16" } }, - "node_modules/pkg-fetch/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/pg": { + "name": "@supabase/pg", + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@supabase/pg/-/pg-0.0.3.tgz", + "integrity": "sha512-WW4VdNYQocmcg7dZYk92vHY2nhhMhxoJhZ20m7PzuKe8p4vSdkv2tSM8HUCpZDiLxC6djfVeMk39ukcwEpFdzg==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "pg-connection-string": "^2.7.0", + "pg-pool": "^3.8.0", + "pg-protocol": "npm:@supabase/pg-protocol@^0.0.2", + "pg-types": "^2.1.0", + "pgpass": "1.x" }, "engines": { - "node": ">=7.0.0" + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } } }, - "node_modules/pkg-fetch/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "license": "MIT", + "optional": true }, - "node_modules/pkg-fetch/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==", - "dev": true, - "engines": { - "node": ">=8" - } + "node_modules/pg-connection-string": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", + "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==", + "license": "MIT" }, - "node_modules/pkg-fetch/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node_modules/pg-format": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz", + "integrity": "sha512-YyKEF78pEA6wwTAqOUaHIN/rWpfzzIuMh9KdAhc3rSLQ/7zkRFcCgYBAEGatDstLyZw4g0s9SNICmaTGnBVeyw==", + "license": "MIT", + "engines": { + "node": ">=4.0" } }, - "node_modules/pkg-fetch/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=4.0.0" } }, - "node_modules/pkg/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/pkg/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, + "node_modules/pg-protocol": { + "name": "@supabase/pg-protocol", + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@supabase/pg-protocol/-/pg-protocol-0.0.2.tgz", + "integrity": "sha512-OLp5LeWRmfJy4vwV753hsCE90vzNSTSAwhsbinCIeoT455DHBufrkktVc4YvPXYEFj+EzpVKw/N0piX+AvBMBg==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz", + "integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.1.0", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/pkg/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/pg/node_modules/pg": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.14.1.tgz", + "integrity": "sha512-0TdbqfjwIun9Fm/r89oB7RFQ0bLgduAhiIqIXOsyKoiC/L54DbuAAzIEN/9Op0f1Po9X7iCPXGoa/Ah+2aI8Xw==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "pg-connection-string": "^2.7.0", + "pg-pool": "^3.8.0", + "pg-protocol": "^1.8.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" }, "engines": { - "node": ">=7.0.0" + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } } }, - "node_modules/pkg/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/pkg/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==", - "dev": true, - "engines": { - "node": ">=8" + "node_modules/pg/node_modules/pg-pool": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.8.0.tgz", + "integrity": "sha512-VBw3jiVm6ZOdLBTIcXLNdSotb6Iy3uOCwDGFAksZCXmi10nyRvnP2v3jl4d+IsLYRyXf6o9hIm/ZtUzlByNUdw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" } }, - "node_modules/pkg/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, + "node_modules/pg/node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "node_modules/pg/node_modules/pg/node_modules/pg-protocol": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.8.0.tgz", + "integrity": "sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==", + "license": "MIT" }, - "node_modules/postgres-array": { + "node_modules/pg/node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/postgres-bytea": { + "node_modules/pg/node_modules/postgres-bytea": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/postgres-date": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.5.tgz", - "integrity": "sha512-pdau6GRPERdAYUQwkBnGKxEfPyhVZXG/JiS44iZWiNdSOWE09N2lUgN6yshuq6fVSon4Pm0VMXd1srUUkLe9iA==", + "node_modules/pg/node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/postgres-interval": { + "node_modules/pg/node_modules/postgres-interval": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", "dependencies": { "xtend": "^4.0.0" }, @@ -4682,596 +5368,812 @@ "node": ">=0.10.0" } }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", - "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" + "node_modules/pgsql-deparser": { + "version": "17.11.1", + "resolved": "https://registry.npmjs.org/pgsql-deparser/-/pgsql-deparser-17.11.1.tgz", + "integrity": "sha512-BGKgwC4qs+FPcG8Ai989LO6i4E8KF5HEvlTnI8uhS4qUyu6P1xCyP9pJDky95ZL8DolaGUDFAJtxteDBw33OCg==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pgsql/types": "^17.6.1" } }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, + "node_modules/pgsql-parser": { + "version": "17.8.2", + "resolved": "https://registry.npmjs.org/pgsql-parser/-/pgsql-parser-17.8.2.tgz", + "integrity": "sha512-/uHZL7mq3Bj23v/nDShb8gN8LwUKdejFii6IFBxRYRXxWlRrbsdky1KtevIxiVGasWZfI+E5t1//Wq+D3cVmAg==", + "license": "MIT", "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "@pgsql/types": "17.6.1", + "libpg-query": "17.6.0", + "pgsql-deparser": "17.11.1" } }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "engines": { - "node": ">=6" - } + "license": "ISC" }, - "node_modules/qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "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": ">=0.6" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/queue-microtask": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.2.tgz", - "integrity": "sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg==", - "dev": true - }, - "node_modules/quick-format-unescaped": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz", - "integrity": "sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A==", - "dev": true - }, - "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, "engines": { - "node": ">=8" + "node": ">=0.10" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=4" } }, - "node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, + "node_modules/pino": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz", + "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", + "license": "MIT", "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^4.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": "^3.0.0" }, - "engines": { - "node": ">=4" + "bin": { + "pino": "bin.js" } }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" + "split2": "^4.0.0" } }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/pino-pretty": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.2.tgz", + "integrity": "sha512-3cN0tCakkT4f3zo9RXDIhy6GTvtYD6bK4CRBLN9j3E/ePqN1tugAXD5rGVfoChW6s0hiek+eyYlLNqc/BG7vBQ==", "dev": true, + "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "secure-json-parse": "^4.0.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^5.0.2" }, - "engines": { - "node": ">=8" + "bin": { + "pino-pretty": "bin.js" } }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/pino-pretty/node_modules/secure-json-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.0.0.tgz", + "integrity": "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==", "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "node_modules/pino/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/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || >=14" } }, - "node_modules/read-pkg-up/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "obuf": "~1.1.2" }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/read-pkg-up/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==", - "dev": true, + "node_modules/postgres-date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/read-pkg-up/node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, + "node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/read-pkg-up/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, + "node_modules/postgres-range": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==", + "license": "MIT" + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, "engines": { - "node": ">=8" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/read-pkg-up/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==", - "dev": true, + "node_modules/prettier-plugin-sql": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-sql/-/prettier-plugin-sql-0.17.1.tgz", + "integrity": "sha512-CR9UpTkUSC/f69AV597hnYcBo77iUhsBPkUER7BUa4YHRRtRUJGfL5LDoHAlUHWGTZNiJdHHELlzK6I3R9XuAw==", + "license": "MIT", + "dependencies": { + "jsox": "^1.2.118", + "node-sql-parser": "^4.11.0", + "sql-formatter": "^14.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=8" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + }, + "peerDependencies": { + "prettier": "^3.0.3" } }, - "node_modules/read-pkg/node_modules/path-type": { + "node_modules/process-warning": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", + "license": "MIT" + }, + "node_modules/prom-client": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.2.0.tgz", + "integrity": "sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA==", + "license": "Apache-2.0", "dependencies": { - "pify": "^3.0.0" + "tdigest": "^0.1.1" }, "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "dev": true, + "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "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/railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", + "license": "CC0-1.0" + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" }, "engines": { - "node": ">=8.10.0" + "node": ">=0.12" + } + }, + "node_modules/randexp/node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "license": "MIT", + "engines": { + "node": ">=0.12" } }, - "node_modules/redent": { + "node_modules/read-pkg": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", "dev": true, + "license": "MIT", "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "node_modules/read-pkg/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, + "license": "MIT", "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "pify": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "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": ">=0.10.0" + "node": ">=8.10.0" } }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, + "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": ">=0.10" + "node": ">= 12.13.0" } }, - "node_modules/repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, + "license": "MIT", "dependencies": { - "is-finite": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" + "node": ">= 0.4" }, - "engines": { - "node": ">= 6" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/request-progress": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, + "license": "MIT", "dependencies": { - "throttleit": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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=", - "dev": true, + "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==", - "dev": true + "node_modules/require-in-the-middle": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + }, + "engines": { + "node": ">=8.6.0" + } }, "node_modules/resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", "dependencies": { - "path-parse": "^1.0.6" + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.4.3.tgz", + "integrity": "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==", + "license": "MIT", "engines": { - "node": ">=0.12" + "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==", - "dev": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, "node_modules/rfdc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.2.0.tgz", - "integrity": "sha512-ijLyszTMmUrXvjSooucVQwimGUk84eRcmCuLV8Xghe3UO85mjUtRAHRyoMM6XtyqbECaXuBWx18La3523sXINA==", - "dev": true + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", "dev": true, + "license": "ISC", "dependencies": { - "glob": "^7.1.3" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", - "dev": true - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "node_modules/rollup": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.37.0.tgz", + "integrity": "sha512-iAtQy/L4QFU+rTJ1YUjXqJOJzuwEghqWzCEYD2FEghT7Gsy1VdABntrO4CLopA5IkflTyqNiLNwPcOJ3S7UKLg==", "dev": true, + "license": "MIT", "dependencies": { - "ret": "~0.1.10" - } + "@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.37.0", + "@rollup/rollup-android-arm64": "4.37.0", + "@rollup/rollup-darwin-arm64": "4.37.0", + "@rollup/rollup-darwin-x64": "4.37.0", + "@rollup/rollup-freebsd-arm64": "4.37.0", + "@rollup/rollup-freebsd-x64": "4.37.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.37.0", + "@rollup/rollup-linux-arm-musleabihf": "4.37.0", + "@rollup/rollup-linux-arm64-gnu": "4.37.0", + "@rollup/rollup-linux-arm64-musl": "4.37.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.37.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.37.0", + "@rollup/rollup-linux-riscv64-gnu": "4.37.0", + "@rollup/rollup-linux-riscv64-musl": "4.37.0", + "@rollup/rollup-linux-s390x-gnu": "4.37.0", + "@rollup/rollup-linux-x64-gnu": "4.37.0", + "@rollup/rollup-linux-x64-musl": "4.37.0", + "@rollup/rollup-win32-arm64-msvc": "4.37.0", + "@rollup/rollup-win32-ia32-msvc": "4.37.0", + "@rollup/rollup-win32-x64-msvc": "4.37.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/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/safe-regex2": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", - "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", + "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": { - "ret": "~0.2.0" + "queue-microtask": "^1.2.2" } }, - "node_modules/safe-regex2/node_modules/ret": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", - "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, "engines": { - "node": ">=4" + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/secure-json-parse": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.3.1.tgz", - "integrity": "sha512-5uGhQLHSC9tVa7RGPkSwxbZVsJCZvIODOadAimCXkU1aCa1fWdszj2DktcutK8A7dD58PoRdxTYiy0jFl6qjnw==", - "dev": true - }, - "node_modules/semver": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", - "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=", - "bin": { - "semver": "bin/semver" + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/semver-store": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", - "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==", - "dev": true - }, - "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "node_modules/safe-regex2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz", + "integrity": "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "ret": "~0.4.0" } }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "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/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, "node_modules/set-cookie-parser": { - "version": "2.4.8", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", - "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==", - "dev": true + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, + "license": "MIT", "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, + "license": "MIT", "dependencies": { - "is-extendable": "^0.1.0" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^1.0.0" }, @@ -5282,1360 +6184,1345 @@ "node_modules/shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", - "dev": true - }, - "node_modules/signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" + "node": ">= 0.4" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", + "license": "BSD-2-Clause" }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, + "license": "MIT", "dependencies": { - "is-descriptor": "^1.0.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" + "node": ">= 0.4" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/snapdragon-node/node_modules/is-data-descriptor": { + "node_modules/side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, + "license": "MIT", "dependencies": { - "kind-of": "^6.0.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "node": ">= 0.4" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, + "license": "MIT", "dependencies": { - "kind-of": "^3.2.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, + "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, - "dependencies": { - "ms": "2.0.0" - } + "license": "ISC" }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, + "license": "MIT", "dependencies": { - "is-descriptor": "^0.1.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/sonic-boom": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.0.tgz", - "integrity": "sha512-1xUAszhQBOrjk7uisbStQZYkZxD3vkYlCUw5qzOblWQ1ILN5v0dVPAs+QPgszzoPmbdWx6jyT9XiLJ95JdlLiQ==", - "dev": true, - "dependencies": { - "atomic-sleep": "^1.0.0", - "flatstr": "^1.0.12" - } - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "atomic-sleep": "^1.0.0" } }, - "node_modules/source-map-support/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==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, "node_modules/spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, + "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "node_modules/spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true, + "license": "CC0-1.0" }, - "node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dependencies": { - "through": "2" - }, + "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": "*" + "node": ">= 10.x" } }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, + "node_modules/sql-formatter": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-14.0.0.tgz", + "integrity": "sha512-VcHYMRvZqg3RNjjxNB/puT9O1hR5QLXTvgTaBtxXcvmRQwSnH9M+oW2Ti+uFuVVU8HoNlOjU2uKHv8c0FQNsdQ==", + "license": "MIT", "dependencies": { - "extend-shallow": "^3.0.0" + "argparse": "^2.0.1", + "get-stdin": "=8.0.0", + "nearley": "^2.20.1" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "sql-formatter": "bin/sql-formatter-cli.cjs" } }, - "node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, - "dependencies": { - "readable-stream": "^3.0.0" - } + "license": "MIT" }, - "node_modules/split2/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "node_modules/std-env": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz", + "integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==", "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "license": "MIT" }, - "node_modules/sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", + "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", "dev": true, + "license": "MIT", "dependencies": { - "is-descriptor": "^0.1.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stream-meter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", - "integrity": "sha1-Uq+Vql6nYKJJFxZwTb/5D3Ov3R0=", - "dev": true, - "dependencies": { - "readable-stream": "^2.1.4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string-similarity": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", - "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==", - "dev": true - }, - "node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, + "license": "MIT", "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string.prototype.padend": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz", - "integrity": "sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==", + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, + "license": "MIT", "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - }, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=4" } }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "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": { - "ansi-regex": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, + "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==", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "node_modules/tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "license": "MIT", + "dependencies": { + "bintrees": "1.0.2" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, + "license": "ISC", "dependencies": { - "min-indent": "^1.0.0" + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, - "engines": { - "node": ">=0.10.0" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/test-exclude/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, + "license": "ISC", "dependencies": { - "has-flag": "^3.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">=4" + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", - "dev": true + "node_modules/test-exclude/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "node_modules/test-exclude/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" }, - "node_modules/tiny-lru": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", - "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==", + "node_modules/test-exclude/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": ">=6" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "node_modules/test-exclude/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "kind-of": "^3.0.2" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "fdir": "^6.4.4", + "picomatch": "^4.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "engines": { - "node": ">=0.10.0" + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "node_modules/tinyglobby/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, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": "^18.0.0 || >=20.0.0" } }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.6" + "node": ">=14.0.0" } }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "is-number": "^7.0.0" }, "engines": { - "node": ">=0.8" + "node": ">=8.0" } }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" + "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/trim-newlines": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", - "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, - "engines": { - "node": ">=8" + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" } }, "node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": 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", - "source-map-support": "^0.5.17", + "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" }, - "engines": { - "node": ">=10.0.0" + "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/ts-node-dev": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.1.6.tgz", - "integrity": "sha512-RTUi7mHMNQospArGz07KiraQcdgUVNXKsgO2HAi7FoiyPMdTDqdniB6K1dqyaIxT7c9v/VpSbfBZPS6uVpaFLQ==", + "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==", + "license": "0BSD" + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, + "license": "MIT", "dependencies": { - "chokidar": "^3.5.1", - "dateformat": "~1.0.4-1.2.3", - "dynamic-dedupe": "^0.3.0", - "minimist": "^1.2.5", - "mkdirp": "^1.0.4", - "resolve": "^1.0.0", - "rimraf": "^2.6.1", - "source-map-support": "^0.5.12", - "tree-kill": "^1.2.2", - "ts-node": "^9.0.0", - "tsconfig": "^7.0.0" - }, - "bin": { - "ts-node-dev": "lib/bin.js", - "tsnd": "lib/bin.js" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" }, "engines": { - "node": ">=0.8.0" + "node": ">= 0.4" } }, - "node_modules/ts-node-dev/node_modules/camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, + "license": "MIT", "dependencies": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ts-node-dev/node_modules/dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, + "license": "MIT", "dependencies": { - "get-stdin": "^4.0.1", - "meow": "^3.3.0" - }, - "bin": { - "dateformat": "bin/cli.js" + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" }, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ts-node-dev/node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, + "license": "MIT", "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ts-node-dev/node_modules/indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, - "dependencies": { - "repeating": "^2.0.0" + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">=0.10.0" + "node": ">=14.17" } }, - "node_modules/ts-node-dev/node_modules/load-json-file": { + "node_modules/unbox-primitive": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, + "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ts-node-dev/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/ts-node-dev/node_modules/meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", "dependencies": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" + "punycode": "^2.1.0" } }, - "node_modules/ts-node-dev/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } + "license": "MIT" }, - "node_modules/ts-node-dev/node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, - "node_modules/ts-node-dev/node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "pinkie-promise": "^2.0.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-node-dev/node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "bin": { + "vite": "bin/vite.js" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-node-dev/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-node-dev/node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-node-dev/node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-node-dev/node_modules/redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "dependencies": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, - "engines": { - "node": ">=0.10.0" + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/ts-node-dev/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "node_modules/vite-node": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.9.tgz", + "integrity": "sha512-w3Gdx7jDcuT9cNn9jExXgOyKmf5UOTb6WMHz8LGAm54eS1Elf5OuBhCxl6zJxGhEeIkgsE1WbHuoL0mj/UXqXg==", "dev": true, + "license": "MIT", "dependencies": { - "glob": "^7.1.3" + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" }, "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/ts-node-dev/node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "dependencies": { - "is-utf8": "^0.2.0" + "vite-node": "vite-node.mjs" }, "engines": { - "node": ">=0.10.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/ts-node-dev/node_modules/strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "node_modules/vite/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", "dev": true, - "dependencies": { - "get-stdin": "^4.0.1" - }, - "bin": { - "strip-indent": "cli.js" + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "engines": { - "node": ">=0.10.0" + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/ts-node-dev/node_modules/trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "node_modules/vite/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", + "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/vitest": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.9.tgz", + "integrity": "sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ==", "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/expect": "3.0.9", + "@vitest/mocker": "3.0.9", + "@vitest/pretty-format": "^3.0.9", + "@vitest/runner": "3.0.9", + "@vitest/snapshot": "3.0.9", + "@vitest/spy": "3.0.9", + "@vitest/utils": "3.0.9", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.1.0", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.0.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, "engines": { - "node": ">=0.3.1" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.0.9", + "@vitest/ui": "3.0.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } } }, - "node_modules/tsconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", - "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, + "license": "ISC", "dependencies": { - "@types/strip-bom": "^3.0.0", - "@types/strip-json-comments": "0.0.30", - "strip-bom": "^3.0.0", - "strip-json-comments": "^2.0.0" + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "^5.0.1" + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" }, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "dev": true, + "license": "MIT", "dependencies": { - "prelude-ls": "~1.1.2" + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, - "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typescript": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", - "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" }, "engines": { - "node": ">=4.2.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/uid2": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", - "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=", - "dev": true - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, + "license": "MIT", "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/unique-temp-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz", - "integrity": "sha1-bc6VsmgcoAPuv7MEpBX5y6vMU4U=", + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", "dependencies": { - "mkdirp": "^0.5.1", - "os-tmpdir": "^1.0.1", - "uid2": "0.0.3" + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">= 4.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "node_modules/wrap-ansi-cjs/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==", "dev": true, + "license": "MIT", "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { - "isarray": "1.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=0.10.0" + "node": ">=7.0.0" } }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, - "dependencies": { - "punycode": "^2.1.0" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, - "bin": { - "uuid": "bin/uuid" - } + "license": "MIT" }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, + "license": "MIT", "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true, + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" + "ansi-regex": "^6.0.1" }, - "bin": { - "which": "bin/which" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2" + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "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/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "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==", - "dev": true - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.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": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/yargs-parser/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dev": true, - "dependencies": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" + "node": ">= 14" } }, "node_modules/yn": { @@ -6643,5360 +7530,10 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", - "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==", - "dev": true - }, - "@babel/runtime": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", - "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@fastify/forwarded": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-1.0.0.tgz", - "integrity": "sha512-VoO+6WD0aRz8bwgJZ8pkkxjq7o/782cQ1j945HWg0obZMgIadYW3Pew0+an+k1QL7IPZHM3db5WF6OP6x4ymMA==", - "dev": true - }, - "@fastify/proxy-addr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-3.0.0.tgz", - "integrity": "sha512-ty7wnUd/GeSqKTC2Jozsl5xGbnxUnEFC0On2/zPv/8ixywipQmVZwuWvNGnBoitJ2wixwVqofwXNua8j6Y62lQ==", - "dev": true, - "requires": { - "@fastify/forwarded": "^1.0.0", - "ipaddr.js": "^2.0.0" - } - }, - "@hapi/bourne": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz", - "integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg==", - "dev": true - }, - "@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", - "dev": true, - "requires": { - "call-me-maybe": "^1.0.1", - "glob-to-regexp": "^0.3.0" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.3", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.3", - "fastq": "^1.6.0" - } - }, - "@sinclair/typebox": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.16.7.tgz", - "integrity": "sha512-d12AkLZJXD30hXBhJSgf33RqGO0NMHIDzsQPYfp6WGoaSuMnSGIUanII2OUbeZFnD/j3Nbl2zifgO2+5tPClCQ==" - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/crypto-js": { - "version": "3.1.47", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-3.1.47.tgz", - "integrity": "sha512-eI6gvpcGHLk3dAuHYnRCAjX+41gMv1nz/VP55wAe5HtmAKDOoPSfr3f6vkMc08ov1S0NsjvUBxDtHHxqQY1LGA==", - "dev": true - }, - "@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", - "dev": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true - }, - "@types/minimist": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", - "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", - "dev": true - }, - "@types/node": { - "version": "14.0.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.13.tgz", - "integrity": "sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA==", - "dev": true - }, - "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true - }, - "@types/pg": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-7.14.3.tgz", - "integrity": "sha512-go5zddQ1FrUQHeBvqPzQ1svKo4KKucSwvqLsvwc/EIuQ9sxDA21b68xc/RwhzAK5pPCnez8NrkYatFIGdJBVvA==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/pg-types": "*" - } - }, - "@types/pg-format": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/pg-format/-/pg-format-1.0.1.tgz", - "integrity": "sha512-TC073gk+fvWGenuE7sDEjmt33HJkNhKJ9uLRgP9O9KQGSUaN1hA7Snbr63EU9/aE1X4Zz0+1BXhSBdZCYFWXog==", - "dev": true - }, - "@types/pg-types": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@types/pg-types/-/pg-types-1.11.5.tgz", - "integrity": "sha512-L8ogeT6vDzT1vxlW3KITTCt+BVXXVkLXfZ/XNm6UqbcJgxf+KPO7yjWx7dQQE8RW07KopL10x2gNMs41+IkMGQ==", - "dev": true - }, - "@types/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", - "dev": true - }, - "@types/strip-json-comments": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", - "dev": true - }, - "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==", - "dev": true - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "args": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", - "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", - "dev": true, - "requires": { - "camelcase": "5.0.0", - "chalk": "2.4.2", - "leven": "2.1.0", - "mri": "1.1.4" - }, - "dependencies": { - "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", - "dev": true - } - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "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==", - "dev": true - }, - "avvio": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.1.tgz", - "integrity": "sha512-b+gox68dqD6c3S3t+bZBKN6rYbVWdwpN12sHQLFTiacDT2rcq7fm07Ww+IKt/AvAkyCIe1f5ArP1bC/vAlx97A==", - "dev": true, - "requires": { - "archy": "^1.0.0", - "debug": "^4.0.0", - "fastq": "^1.6.1", - "queue-microtask": "^1.1.2" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", - "dev": true - }, - "axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dev": true, - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "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, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "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 - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" - }, - "byline": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", - "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", - "dev": true - }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "dependencies": { - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "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, - "requires": { - "is-glob": "^4.0.1" - } - }, - "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 - }, - "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, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "dev": true - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cp-file": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-7.0.0.tgz", - "integrity": "sha512-0Cbj7gyvFVApzpK/uhCtQ/9kE9UnYpxMzaq5nQQC/Dh4iaj5fxp7iEFIullrYwzj8nf0qnsI1Qsx34hAeAebvw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "nested-error-stacks": "^2.0.0", - "p-event": "^4.1.0" - } - }, - "cpy": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/cpy/-/cpy-8.1.1.tgz", - "integrity": "sha512-vqHT+9o67sMwJ5hUd/BAOYeemkU+MuFRsK2c36Xc3eefQpAsp1kAsyDxEDcc5JS1+y9l/XHPrIsVTcyGGmkUUQ==", - "dev": true, - "requires": { - "arrify": "^2.0.1", - "cp-file": "^7.0.0", - "globby": "^9.2.0", - "has-glob": "^1.0.0", - "junk": "^3.1.0", - "nested-error-stacks": "^2.1.0", - "p-all": "^2.1.0", - "p-filter": "^2.1.0", - "p-map": "^3.0.0" - }, - "dependencies": { - "@nodelib/fs.stat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", - "dev": true - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "dev": true, - "requires": { - "path-type": "^3.0.0" - } - }, - "fast-glob": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", - "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", - "dev": true, - "requires": { - "@mrmlnc/readdir-enhanced": "^2.2.1", - "@nodelib/fs.stat": "^1.1.2", - "glob-parent": "^3.1.0", - "is-glob": "^4.0.0", - "merge2": "^1.2.3", - "micromatch": "^3.1.10" - } - }, - "globby": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", - "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", - "dev": true, - "requires": { - "@types/glob": "^7.1.1", - "array-union": "^1.0.2", - "dir-glob": "^2.2.2", - "fast-glob": "^2.2.6", - "glob": "^7.1.3", - "ignore": "^4.0.3", - "pify": "^4.0.1", - "slash": "^2.0.0" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - } - } - }, - "cpy-cli": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/cpy-cli/-/cpy-cli-3.1.1.tgz", - "integrity": "sha512-HCpNdBkQy3rw+uARLuIf0YurqsMXYzBa9ihhSAuxYJcNIrqrSq3BstPfr0cQN38AdMrQiO9Dp4hYy7GtGJsLPg==", - "dev": true, - "requires": { - "cpy": "^8.0.0", - "meow": "^6.1.1" - } - }, - "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==", - "dev": true - }, - "crypto-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz", - "integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==", - "dev": true - }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "requires": { - "array-find-index": "^1.0.1" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "dateformat": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.5.1.tgz", - "integrity": "sha512-OD0TZ+B7yP7ZgpJf5K2DIbj3FZvFvxgFUuaqA/V5zTjAtAAXZ1E8bktHxmAGs4x5b7PflqA9LeQ84Og7wYtF7Q==", - "dev": true - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "dev": true, - "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - } - } - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "dynamic-dedupe": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", - "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", - "dev": true, - "requires": { - "xtend": "^4.0.0" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "encoding-negotiator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/encoding-negotiator/-/encoding-negotiator-2.0.1.tgz", - "integrity": "sha512-GSK7qphNR4iPcejfAlZxKDoz3xMhnspwImK+Af5WhePS9jUpK/Oh7rUdyENWu+9rgDflOCTmAojBsgsvM8neAQ==", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", - "dev": true - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "dev": true - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "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==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, - "fast-glob": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.2.tgz", - "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" - }, - "dependencies": { - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "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 - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "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, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "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 - }, - "fast-json-stringify": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.5.2.tgz", - "integrity": "sha512-H0/Wq7jj/i96DycCySdh+Jl0MubqaScM51GJkdJmjzwyiNpxAgQ9deYqk2H8SNOfwPj21RtTXMkMV5GosUWKeQ==", - "dev": true, - "requires": { - "ajv": "^6.11.0", - "deepmerge": "^4.2.2", - "rfdc": "^1.2.0", - "string-similarity": "^4.0.1" - } - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fast-redact": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.0.0.tgz", - "integrity": "sha512-a/S/Hp6aoIjx7EmugtzLqXmcNsyFszqbt6qQ99BdG61QjBZF6shNis0BYR6TsZOQ1twYc0FN2Xdhwwbv6+KD0w==", - "dev": true - }, - "fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", - "dev": true - }, - "fastify": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.14.0.tgz", - "integrity": "sha512-a6W2iVPJMOaULqCykJ5nFRtnoknqt9K3b6rqAQcGjT/O2Hy+vvo+9/+cL2907KN0iF/91Ke+XQluKrVNF6+Z7w==", - "dev": true, - "requires": { - "@fastify/proxy-addr": "^3.0.0", - "abstract-logging": "^2.0.0", - "ajv": "^6.12.2", - "avvio": "^7.1.2", - "fast-json-stringify": "^2.5.0", - "fastify-error": "^0.3.0", - "fastify-warning": "^0.2.0", - "find-my-way": "^4.0.0", - "flatstr": "^1.0.12", - "light-my-request": "^4.2.0", - "pino": "^6.2.1", - "readable-stream": "^3.4.0", - "rfdc": "^1.1.4", - "secure-json-parse": "^2.0.0", - "semver": "^7.3.2", - "tiny-lru": "^7.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "fastify-cors": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-5.2.0.tgz", - "integrity": "sha512-Lde71qT23M3Ip3pmny3uN6q6lQ4J5J0/YWoqe2stL4sMT99R5LEtTJ2La2zijFOR5OFhxvxqGgR7BIc2x3amPg==", - "dev": true, - "requires": { - "fastify-plugin": "^3.0.0", - "vary": "^1.1.2" - } - }, - "fastify-error": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.0.tgz", - "integrity": "sha512-Jm2LMTB5rsJqlS1+cmgqqM9tTs0UrlgYR7TvDT3ZgXsUI5ib1NjQlqZHf+tDK5tVPdFGwyq02wAoJtyYIRSiFA==", - "dev": true - }, - "fastify-plugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz", - "integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w==", - "dev": true - }, - "fastify-static": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.2.2.tgz", - "integrity": "sha512-C631EuGdMlUzFXuuP4SqXBoQEiit9S0uYRd97cF2mFhgStvZvQKIjtzUk/GMQu6EfEdm0ddj3UAc0C6dVeNyKA==", - "dev": true, - "requires": { - "content-disposition": "^0.5.3", - "encoding-negotiator": "^2.0.1", - "fastify-plugin": "^3.0.0", - "glob": "^7.1.4", - "readable-stream": "^3.4.0", - "send": "^0.17.1" - }, - "dependencies": { - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "fastify-swagger": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/fastify-swagger/-/fastify-swagger-4.7.0.tgz", - "integrity": "sha512-IgmHuA8BGpBrvLninqDBQdlJEiB3yEg3XYYmqIcqQqWmU7KYNTQokWNTKG8nBPjj/P2wyTJ0fNxm/K2Fljq/hw==", - "dev": true, - "requires": { - "fastify-plugin": "^3.0.0", - "fastify-static": "^4.0.0", - "js-yaml": "^4.0.0", - "json-schema-resolver": "^1.2.0", - "openapi-types": "^8.0.0" - }, - "dependencies": { - "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 - }, - "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, - "requires": { - "argparse": "^2.0.1" - } - } - } - }, - "fastify-warning": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", - "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==", - "dev": true - }, - "fastq": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", - "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "find-my-way": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.0.0.tgz", - "integrity": "sha512-IrICzn/Xm5r5A3RCB8rGLNe+dvzZl+SiUugTQOUMLJciP2qiSu2hw9JtVEYV3VruAYzSWjo/MLH9CFKtVdlWhQ==", - "dev": true, - "requires": { - "fast-decode-uri-component": "^1.0.1", - "fast-deep-equal": "^3.1.3", - "safe-regex2": "^2.0.0", - "semver-store": "^0.3.0" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", - "dev": true - } - } - }, - "flatstr": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", - "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==", - "dev": true - }, - "follow-redirects": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz", - "integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==", - "dev": true - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "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==", - "dev": true - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", - "dev": true - }, - "globby": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz", - "integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-glob/-/has-glob-1.0.0.tgz", - "integrity": "sha1-mqqe7b/7G6OZCnsAEPtnjuAIEgc=", - "dev": true, - "requires": { - "is-glob": "^3.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "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 - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "ignore": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "into-stream": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-5.1.1.tgz", - "integrity": "sha512-krrAJ7McQxGGmvaYbB7Q1mcA+cRwg9Ij2RfWIeVesNBgVDZmzY/Fa4IpZUT3bmdRzMzdf/mzltCG2Dq99IZGBA==", - "dev": true, - "requires": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" - } - }, - "ipaddr.js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.0.tgz", - "integrity": "sha512-S54H9mIj0rbxRIyrDMEuuER86LdlgUg9FSeZ8duQb6CUG2iRrA36MYVQBSprTF/ZeAwvyQ5mDGuNvIPM0BIl3w==", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "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, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "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==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", - "dev": true - }, - "joycon": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-2.2.5.tgz", - "integrity": "sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-1.2.2.tgz", - "integrity": "sha512-sW4b4BDJzYiKpJind7l1JtH3P1yn43vCv3w51YR2Ixse5rXr006TL10gM0Ek54pET6vxwiWq5RQuIMgmH9YrrQ==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "rfdc": "^1.1.4", - "uri-js": "^4.2.2" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "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 - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "junk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", - "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "light-my-request": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.4.1.tgz", - "integrity": "sha512-FDNRF2mYjthIRWE7O8d/X7AzDx4otQHl4/QXbu3Q/FRwBFcgb+ZoDaUd5HwN53uQXLAiw76osN+Va0NEaOW6rQ==", - "dev": true, - "requires": { - "ajv": "^6.12.2", - "cookie": "^0.4.0", - "fastify-warning": "^0.2.0", - "readable-stream": "^3.6.0", - "set-cookie-parser": "^2.4.1" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2" - } - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", - "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", - "dev": true - }, - "meow": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz", - "integrity": "sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "^4.0.2", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "merge2": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", - "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "dev": true - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dev": true, - "requires": { - "mime-db": "1.44.0" - } - }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "dependencies": { - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - } - } - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "mocha": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz", - "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==", - "dev": true, - "requires": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "chokidar": "3.3.0", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "3.0.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.5", - "ms": "2.1.1", - "node-environment-flags": "1.0.6", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" - }, - "dependencies": { - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", - "dev": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "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, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "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 - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", - "dev": true, - "requires": { - "picomatch": "^2.0.4" - } - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "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, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "mri": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", - "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "multistream": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/multistream/-/multistream-2.1.1.tgz", - "integrity": "sha512-xasv76hl6nr1dEy3lPvy7Ej7K/Lx3O/FCvwge8PeVJpciPPoNCbaANcNiBug3IpdvTveZUcAV0DJzdnUDMesNQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.5" - } - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "nested-error-stacks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", - "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "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 - }, - "npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "openapi-types": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-8.0.0.tgz", - "integrity": "sha512-dcHYyCDOAy4QQTrur5Sn1L3lPVspB7rd04Rw/Q7AsMvfV797IiWgmKziFCbq8VhnBoREU/SPPSBDxtK9Biwa1g==", - "dev": true - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-all": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-all/-/p-all-2.1.0.tgz", - "integrity": "sha512-HbZxz5FONzz/z2gJfk6bFca0BCiSRF8jU3yCsWOen/vR6lZjfPOu/e7L3uFzTW1i0H8TlC3vqQstEJPQL4/uLA==", - "dev": true, - "requires": { - "p-map": "^2.0.0" - }, - "dependencies": { - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true - } - } - }, - "p-event": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", - "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", - "dev": true, - "requires": { - "p-timeout": "^3.1.0" - } - }, - "p-filter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", - "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", - "dev": true, - "requires": { - "p-map": "^2.0.0" - }, - "dependencies": { - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true - } - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-is-promise": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", - "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dev": true, - "requires": { - "p-finally": "^1.0.0" - } - }, - "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==", - "dev": true - }, - "packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "pg": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/pg/-/pg-7.18.2.tgz", - "integrity": "sha512-Mvt0dGYMwvEADNKy5PMQGlzPudKcKKzJds/VbOeZJpb6f/pI3mmoXX0JksPgI3l3JPP/2Apq7F36O63J7mgveA==", - "requires": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "0.1.3", - "pg-packet-stream": "^1.1.0", - "pg-pool": "^2.0.10", - "pg-types": "^2.1.0", - "pgpass": "1.x", - "semver": "4.3.2" - } - }, - "pg-connection-string": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", - "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" - }, - "pg-format": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz", - "integrity": "sha1-J3NCNsKtP05QZJFaWTNOIAQKgo4=" - }, - "pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" - }, - "pg-packet-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pg-packet-stream/-/pg-packet-stream-1.1.0.tgz", - "integrity": "sha512-kRBH0tDIW/8lfnnOyTwKD23ygJ/kexQVXZs7gEyBljw4FYqimZFxnMMx50ndZ8In77QgfGuItS5LLclC2TtjYg==" - }, - "pg-pool": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.10.tgz", - "integrity": "sha512-qdwzY92bHf3nwzIUcj+zJ0Qo5lpG/YxchahxIN8+ZVmXqkahKXsnl2aiJPHLYN9o5mB/leG+Xh6XKxtP7e0sjg==" - }, - "pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "requires": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - } - }, - "pgpass": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", - "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", - "requires": { - "split": "^1.0.0" - } - }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - }, - "pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pino": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/pino/-/pino-6.11.2.tgz", - "integrity": "sha512-bmzxwbrIPxQUlAuMkF4PWVErUGERU4z37HazlhflKFg08crsNE3fACGN6gPwg5xtKOK47Ux5cZm8YCuLV4wWJg==", - "dev": true, - "requires": { - "fast-redact": "^3.0.0", - "fast-safe-stringify": "^2.0.7", - "flatstr": "^1.0.12", - "pino-std-serializers": "^3.1.0", - "quick-format-unescaped": "4.0.1", - "sonic-boom": "^1.0.2" - } - }, - "pino-pretty": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-4.7.1.tgz", - "integrity": "sha512-ILE5YBpur88FlZ0cr1BNqVjgG9fOoK+md3peqmcs7AC6oq7SNiaJioIcrykMxfNsuygMYjUJtvAcARRE9aRc9w==", - "dev": true, - "requires": { - "@hapi/bourne": "^2.0.0", - "args": "^5.0.1", - "chalk": "^4.0.0", - "dateformat": "^4.5.1", - "fast-safe-stringify": "^2.0.7", - "jmespath": "^0.15.0", - "joycon": "^2.2.5", - "pump": "^3.0.0", - "readable-stream": "^3.6.0", - "split2": "^3.1.1", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "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 - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "pino-std-serializers": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", - "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==", - "dev": true - }, - "pkg": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/pkg/-/pkg-4.4.8.tgz", - "integrity": "sha512-Fqqv0iaX48U3CFZxd6Dq6JKe7BrAWbgRAqMJkz/m8W3H5cqJ6suvsUWe5AJPRlN/AhbBYXBJ0XG9QlYPTXcVFA==", - "dev": true, - "requires": { - "@babel/parser": "^7.9.4", - "@babel/runtime": "^7.9.2", - "chalk": "^3.0.0", - "escodegen": "^1.14.1", - "fs-extra": "^8.1.0", - "globby": "^11.0.0", - "into-stream": "^5.1.1", - "minimist": "^1.2.5", - "multistream": "^2.1.1", - "pkg-fetch": "^2.6.7", - "progress": "^2.0.3", - "resolve": "^1.15.1", - "stream-meter": "^1.0.4" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "pkg-fetch": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-2.6.8.tgz", - "integrity": "sha512-CFG7jOeVD38lltLGA7xCJxYsD//GKLjl1P9tc/n9By2a4WEHQjfkBMrYdMS8WOHVP+r9L20fsZNbaKcubDAiQg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.9.2", - "byline": "^5.0.0", - "chalk": "^3.0.0", - "expand-template": "^2.0.3", - "fs-extra": "^8.1.0", - "minimist": "^1.2.5", - "progress": "^2.0.3", - "request": "^2.88.0", - "request-progress": "^3.0.0", - "semver": "^6.3.0", - "unique-temp-dir": "^1.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" - }, - "postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" - }, - "postgres-date": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.5.tgz", - "integrity": "sha512-pdau6GRPERdAYUQwkBnGKxEfPyhVZXG/JiS44iZWiNdSOWE09N2lUgN6yshuq6fVSon4Pm0VMXd1srUUkLe9iA==" - }, - "postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "requires": { - "xtend": "^4.0.0" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prettier": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", - "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.2.tgz", - "integrity": "sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg==", - "dev": true - }, - "quick-format-unescaped": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz", - "integrity": "sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A==", - "dev": true - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "dependencies": { - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "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==", - "dev": true - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "request-progress": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", - "dev": true, - "requires": { - "throttleit": "^1.0.0" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "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==", - "dev": true - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rfdc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.2.0.tgz", - "integrity": "sha512-ijLyszTMmUrXvjSooucVQwimGUk84eRcmCuLV8Xghe3UO85mjUtRAHRyoMM6XtyqbECaXuBWx18La3523sXINA==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safe-regex2": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", - "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", - "dev": true, - "requires": { - "ret": "~0.2.0" - }, - "dependencies": { - "ret": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", - "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", - "dev": true - } - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "secure-json-parse": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.3.1.tgz", - "integrity": "sha512-5uGhQLHSC9tVa7RGPkSwxbZVsJCZvIODOadAimCXkU1aCa1fWdszj2DktcutK8A7dD58PoRdxTYiy0jFl6qjnw==", - "dev": true - }, - "semver": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", - "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" - }, - "semver-store": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", - "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==", - "dev": true - }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-cookie-parser": { - "version": "2.4.8", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", - "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", - "dev": true - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "sonic-boom": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.0.tgz", - "integrity": "sha512-1xUAszhQBOrjk7uisbStQZYkZxD3vkYlCUw5qzOblWQ1ILN5v0dVPAs+QPgszzoPmbdWx6jyT9XiLJ95JdlLiQ==", - "dev": true, - "requires": { - "atomic-sleep": "^1.0.0", - "flatstr": "^1.0.12" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "requires": { - "through": "2" - } - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dev": true, - "requires": { - "readable-stream": "^3.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true - }, - "stream-meter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", - "integrity": "sha1-Uq+Vql6nYKJJFxZwTb/5D3Ov3R0=", - "dev": true, - "requires": { - "readable-stream": "^2.1.4" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "string-similarity": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", - "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string.prototype.padend": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz", - "integrity": "sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "tiny-lru": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", - "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true - }, - "trim-newlines": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", - "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", - "dev": true - }, - "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } - } - }, - "ts-node-dev": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.1.6.tgz", - "integrity": "sha512-RTUi7mHMNQospArGz07KiraQcdgUVNXKsgO2HAi7FoiyPMdTDqdniB6K1dqyaIxT7c9v/VpSbfBZPS6uVpaFLQ==", - "dev": true, - "requires": { - "chokidar": "^3.5.1", - "dateformat": "~1.0.4-1.2.3", - "dynamic-dedupe": "^0.3.0", - "minimist": "^1.2.5", - "mkdirp": "^1.0.4", - "resolve": "^1.0.0", - "rimraf": "^2.6.1", - "source-map-support": "^0.5.12", - "tree-kill": "^1.2.2", - "ts-node": "^9.0.0", - "tsconfig": "^7.0.0" - }, - "dependencies": { - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - } - }, - "dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.3.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1" - } - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - } - } - }, - "tsconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", - "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", - "dev": true, - "requires": { - "@types/strip-bom": "^3.0.0", - "@types/strip-json-comments": "0.0.30", - "strip-bom": "^3.0.0", - "strip-json-comments": "^2.0.0" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true - }, - "typescript": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", - "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", - "dev": true - }, - "uid2": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", - "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=", - "dev": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unique-temp-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz", - "integrity": "sha1-bc6VsmgcoAPuv7MEpBX5y6vMU4U=", - "dev": true, - "requires": { - "mkdirp": "^0.5.1", - "os-tmpdir": "^1.0.1", - "uid2": "0.0.3" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.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": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } - } - }, - "yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dev": true, - "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - } } } diff --git a/package.json b/package.json old mode 100755 new mode 100644 index a956bc0a..58b95839 --- a/package.json +++ b/package.json @@ -9,46 +9,72 @@ "files": [ "dist" ], - "main": "dist/main/index.js", - "module": "dist/module/index.js", - "repository": "supabase/postgres-meta", + "type": "module", + "main": "dist/lib/index.js", + "types": "dist/lib/index.d.ts", + "imports": { + "#package.json": "./package.json" + }, + "repository": { + "url": "git+https://github.com/supabase/postgres-meta.git" + }, "scripts": { - "clean": "rimraf bin dist", - "format": "prettier --write \"{src,test}/**/*.ts\"", - "build": "run-s clean format build:*", - "build:main": "tsc -p tsconfig.json && cpy 'src/lib/sql/*.sql' dist/main/sql", - "build:module": "tsc -p tsconfig.module.json && cpy 'src/lib/sql/*.sql' dist/module/sql", - "build:server": "tsc -p tsconfig.server.json && cpy 'src/lib/sql/*.sql' bin/src/lib/sql", - "docs:export": "PG_META_EXPORT_DOCS=true ts-node-dev src/server/app.ts", - "start": "ts-node-dev src/server/app.ts | pino-pretty --colorize", - "dev": "run-s clean format start", - "pkg": "run-s clean format build:server && pkg --out-path bin .pkg.config.json", - "test": "run-s build:server && node -r esm ./node_modules/.bin/mocha 'test/**/*.js' --recursive" + "check": "tsc -p tsconfig.json --noEmit", + "clean": "rimraf dist tsconfig.tsbuildinfo", + "format": "prettier --write '{src,test}/**/*.ts' '*.ts'", + "build": "tsc -p tsconfig.json && cpy 'src/lib/sql/*.sql' dist/lib/sql", + "docs:export": "PG_META_EXPORT_DOCS=true node --loader ts-node/esm src/server/server.ts > openapi.json", + "gen:types:typescript": "PG_META_GENERATE_TYPES=typescript node --loader ts-node/esm src/server/server.ts", + "gen:types:go": "PG_META_GENERATE_TYPES=go node --loader ts-node/esm src/server/server.ts", + "gen:types:swift": "PG_META_GENERATE_TYPES=swift node --loader ts-node/esm src/server/server.ts", + "gen:types:python": "PG_META_GENERATE_TYPES=python node --loader ts-node/esm src/server/server.ts", + "start": "node dist/server/server.js", + "dev": "trap 'npm run db:clean' INT && run-s db:clean db:run && run-s dev:code", + "dev:code": "nodemon --exec node --loader ts-node/esm src/server/server.ts | pino-pretty --colorize", + "test": "run-s db:clean db:run test:run db:clean", + "db:clean": "cd test/db && docker compose down", + "db:run": "cd test/db && docker compose up --detach --wait", + "test:run": "PG_META_MAX_RESULT_SIZE_MB=20 PG_QUERY_TIMEOUT_SECS=5 PG_CONN_TIMEOUT_SECS=30 vitest run --coverage", + "test:update": "run-s db:clean db:run && PG_META_MAX_RESULT_SIZE_MB=20 PG_QUERY_TIMEOUT_SECS=5 PG_CONN_TIMEOUT_SECS=30 vitest run --update && run-s db:clean" + }, + "engines": { + "node": ">=20", + "npm": ">=9" }, "dependencies": { - "@sinclair/typebox": "^0.16.7", - "pg": "^7.0.0", - "pg-format": "^1.0.4" + "@fastify/cors": "^9.0.1", + "@fastify/swagger": "^8.2.1", + "@fastify/type-provider-typebox": "^3.5.0", + "@sentry/node": "^9.12.0", + "@sentry/profiling-node": "^9.12.0", + "@sinclair/typebox": "^0.31.25", + "close-with-grace": "^2.1.0", + "crypto-js": "^4.0.0", + "fastify": "^4.24.3", + "fastify-metrics": "^10.0.0", + "pg": "npm:@supabase/pg@0.0.3", + "pg-connection-string": "^2.7.0", + "pg-format": "^1.0.4", + "pg-protocol": "npm:@supabase/pg-protocol@0.0.2", + "pgsql-parser": "^17.8.2", + "pino": "^9.5.0", + "postgres-array": "^3.0.1", + "prettier": "^3.3.3", + "prettier-plugin-sql": "0.17.1" }, "devDependencies": { - "@types/crypto-js": "^3.1.47", - "@types/node": "^14.0.13", - "@types/pg": "^7.14.3", + "@types/crypto-js": "^4.1.1", + "@types/node": "^20.11.14", + "@types/pg": "^8.11.10", "@types/pg-format": "^1.0.1", - "axios": "^0.21.1", - "cpy-cli": "^3.1.1", - "crypto-js": "^4.0.0", - "esm": "^3.2.25", - "fastify": "^3.14.0", - "fastify-cors": "^5.2.0", - "fastify-swagger": "^4.7.0", - "mocha": "^7.1.2", + "@vitest/coverage-v8": "^3.0.5", + "cpy-cli": "^5.0.0", + "nodemon": "^3.1.7", "npm-run-all": "^4.1.5", - "pino-pretty": "^4.7.1", - "pkg": "^4.4.8", - "prettier": "^2.0.5", - "rimraf": "^3.0.2", - "ts-node-dev": "^1.1.6", - "typescript": "^4.3.2" + "pino-pretty": "^13.1.1", + "rimraf": "^6.0.1", + "ts-node": "^10.9.1", + "typescript": "^5.6.3", + "vitest": "^3.0.5" } } diff --git a/scripts/generate-python-types-test.ts b/scripts/generate-python-types-test.ts new file mode 100644 index 00000000..45111f56 --- /dev/null +++ b/scripts/generate-python-types-test.ts @@ -0,0 +1,44 @@ +#!/usr/bin/env node + +/** + * Script to generate Python types for CI validation + * This script uses the test database setup to generate Python types + */ + +import { build } from '../src/server/app.js' + +const TEST_CONNECTION_STRING = 'postgresql://postgres:postgres@localhost:5432' + +async function generatePythonTypes() { + const app = build() + + try { + const response = await app.inject({ + method: 'GET', + url: '/generators/python', + headers: { + pg: TEST_CONNECTION_STRING, + }, + query: { + access_control: 'public', + }, + }) + + if (response.statusCode !== 200) { + console.error(`Failed to generate types: ${response.statusCode}`) + console.error(response.body) + process.exit(1) + } + + // Write to stdout so it can be captured + process.stdout.write(response.body) + } catch (error) { + console.error('Error generating Python types:', error) + process.exit(1) + } finally { + await app.close() + } +} + +generatePythonTypes() + diff --git a/src/lib/Parser.ts b/src/lib/Parser.ts new file mode 100644 index 00000000..ae1741ff --- /dev/null +++ b/src/lib/Parser.ts @@ -0,0 +1,68 @@ +import prettier from 'prettier/standalone.js' +import SqlFormatter from 'prettier-plugin-sql' +import { parse, deparse } from 'pgsql-parser' +import { FormatterOptions } from './types.js' + +const DEFAULT_FORMATTER_OPTIONS = { + plugins: [SqlFormatter], + formatter: 'sql-formatter', + language: 'postgresql', + database: 'postgresql', + parser: 'sql', +} + +/** + * Parses a SQL string into an AST. + */ +export function Parse(sql: string): ParseReturnValues { + try { + const data = parse(sql) + + return { data, error: null } + } catch (error) { + return { data: null, error: error as Error } + } +} +interface ParseReturnValues { + data: object | null + error: null | Error +} + +/** + * Deparses an AST into SQL string. + */ +export async function Deparse(parsedSql: object): Promise { + try { + const data = await deparse(parsedSql, {}) + return { data, error: null } + } catch (error) { + return { data: null, error: error as Error } + } +} +interface DeparseReturnValues { + data: string | null + error: null | Error +} + +/** + * Formats a SQL string into a prettier-formatted SQL string. + */ +export async function Format( + sql: string, + options: FormatterOptions = {} +): Promise { + try { + const formatted = await prettier.format(sql, { + ...DEFAULT_FORMATTER_OPTIONS, + ...options, + }) + + return { data: formatted, error: null } + } catch (error) { + return { data: null, error: error as Error } + } +} +interface FormatReturnValues { + data: string | null + error: null | Error +} diff --git a/src/lib/PostgresMeta.ts b/src/lib/PostgresMeta.ts index 922f9527..eb931624 100644 --- a/src/lib/PostgresMeta.ts +++ b/src/lib/PostgresMeta.ts @@ -1,50 +1,78 @@ -import { PoolConfig } from 'pg' -import PostgresMetaColumns from './PostgresMetaColumns' -import PostgresMetaConfig from './PostgresMetaConfig' -import PostgresMetaExtensions from './PostgresMetaExtensions' -import PostgresMetaFunctions from './PostgresMetaFunctions' -import PostgresMetaPolicies from './PostgresMetaPolicies' -import PostgresMetaPublications from './PostgresMetaPublications' -import PostgresMetaRoles from './PostgresMetaRoles' -import PostgresMetaSchemas from './PostgresMetaSchemas' -import PostgresMetaTables from './PostgresMetaTables' -import PostgresMetaTriggers from './PostgresMetaTriggers' -import PostgresMetaTypes from './PostgresMetaTypes' -import PostgresMetaVersion from './PostgresMetaVersion' -import { init } from './db' -import { PostgresMetaResult } from './types' +import * as Parser from './Parser.js' +import PostgresMetaColumnPrivileges from './PostgresMetaColumnPrivileges.js' +import PostgresMetaColumns from './PostgresMetaColumns.js' +import PostgresMetaConfig from './PostgresMetaConfig.js' +import PostgresMetaExtensions from './PostgresMetaExtensions.js' +import PostgresMetaForeignTables from './PostgresMetaForeignTables.js' +import PostgresMetaFunctions from './PostgresMetaFunctions.js' +import PostgresMetaIndexes from './PostgresMetaIndexes.js' +import PostgresMetaMaterializedViews from './PostgresMetaMaterializedViews.js' +import PostgresMetaPolicies from './PostgresMetaPolicies.js' +import PostgresMetaPublications from './PostgresMetaPublications.js' +import PostgresMetaRelationships from './PostgresMetaRelationships.js' +import PostgresMetaRoles from './PostgresMetaRoles.js' +import PostgresMetaSchemas from './PostgresMetaSchemas.js' +import PostgresMetaTablePrivileges from './PostgresMetaTablePrivileges.js' +import PostgresMetaTables from './PostgresMetaTables.js' +import PostgresMetaTriggers from './PostgresMetaTriggers.js' +import PostgresMetaTypes from './PostgresMetaTypes.js' +import PostgresMetaVersion from './PostgresMetaVersion.js' +import PostgresMetaViews from './PostgresMetaViews.js' +import { init } from './db.js' +import { PostgresMetaResult, PoolConfig } from './types.js' export default class PostgresMeta { - query: (sql: string) => Promise> + query: ( + sql: string, + opts?: { statementQueryTimeout?: number; trackQueryInSentry?: boolean; parameters?: unknown[] } + ) => Promise> end: () => Promise + columnPrivileges: PostgresMetaColumnPrivileges columns: PostgresMetaColumns config: PostgresMetaConfig extensions: PostgresMetaExtensions + foreignTables: PostgresMetaForeignTables functions: PostgresMetaFunctions + indexes: PostgresMetaIndexes + materializedViews: PostgresMetaMaterializedViews policies: PostgresMetaPolicies publications: PostgresMetaPublications + relationships: PostgresMetaRelationships roles: PostgresMetaRoles schemas: PostgresMetaSchemas + tablePrivileges: PostgresMetaTablePrivileges tables: PostgresMetaTables triggers: PostgresMetaTriggers types: PostgresMetaTypes version: PostgresMetaVersion + views: PostgresMetaViews + + parse = Parser.Parse + deparse = Parser.Deparse + format = Parser.Format constructor(config: PoolConfig) { const { query, end } = init(config) this.query = query this.end = end + this.columnPrivileges = new PostgresMetaColumnPrivileges(this.query) this.columns = new PostgresMetaColumns(this.query) this.config = new PostgresMetaConfig(this.query) this.extensions = new PostgresMetaExtensions(this.query) + this.foreignTables = new PostgresMetaForeignTables(this.query) this.functions = new PostgresMetaFunctions(this.query) + this.indexes = new PostgresMetaIndexes(this.query) + this.materializedViews = new PostgresMetaMaterializedViews(this.query) this.policies = new PostgresMetaPolicies(this.query) this.publications = new PostgresMetaPublications(this.query) + this.relationships = new PostgresMetaRelationships(this.query) this.roles = new PostgresMetaRoles(this.query) this.schemas = new PostgresMetaSchemas(this.query) + this.tablePrivileges = new PostgresMetaTablePrivileges(this.query) this.tables = new PostgresMetaTables(this.query) this.triggers = new PostgresMetaTriggers(this.query) this.types = new PostgresMetaTypes(this.query) this.version = new PostgresMetaVersion(this.query) + this.views = new PostgresMetaViews(this.query) } } diff --git a/src/lib/PostgresMetaColumnPrivileges.ts b/src/lib/PostgresMetaColumnPrivileges.ts new file mode 100644 index 00000000..b2a0b6fe --- /dev/null +++ b/src/lib/PostgresMetaColumnPrivileges.ts @@ -0,0 +1,120 @@ +import { ident, literal } from 'pg-format' +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' +import { filterByValue, filterByList } from './helpers.js' +import { COLUMN_PRIVILEGES_SQL } from './sql/column_privileges.sql.js' +import { + PostgresMetaResult, + PostgresColumnPrivileges, + PostgresColumnPrivilegesGrant, + PostgresColumnPrivilegesRevoke, +} from './types.js' + +export default class PostgresMetaColumnPrivileges { + query: (sql: string) => Promise> + + constructor(query: (sql: string) => Promise>) { + this.query = query + } + + async list({ + includeSystemSchemas = false, + includedSchemas, + excludedSchemas, + limit, + offset, + }: { + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + } = {}): Promise> { + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + const sql = COLUMN_PRIVILEGES_SQL({ schemaFilter, limit, offset }) + return await this.query(sql) + } + + async grant( + grants: PostgresColumnPrivilegesGrant[] + ): Promise> { + let sql = ` +do $$ +declare + col record; +begin +${grants + .map(({ privilege_type, column_id, grantee, is_grantable }) => { + const [relationId, columnNumber] = column_id.split('.') + return ` +select * +from pg_attribute a +where a.attrelid = ${literal(relationId)} + and a.attnum = ${literal(columnNumber)} +into col; +execute format( + 'grant ${privilege_type} (%I) on %s to ${ + grantee.toLowerCase() === 'public' ? 'public' : ident(grantee) + } ${is_grantable ? 'with grant option' : ''}', + col.attname, + col.attrelid::regclass +);` + }) + .join('\n')} +end $$; +` + const { data, error } = await this.query(sql) + if (error) { + return { data, error } + } + + // Return the updated column privileges for modified columns. + const columnIds = [...new Set(grants.map(({ column_id }) => column_id))] + const columnIdsFilter = filterByValue(columnIds) + sql = COLUMN_PRIVILEGES_SQL({ columnIdsFilter }) + return await this.query(sql) + } + + async revoke( + revokes: PostgresColumnPrivilegesRevoke[] + ): Promise> { + let sql = ` +do $$ +declare + col record; +begin +${revokes + .map(({ privilege_type, column_id, grantee }) => { + const [relationId, columnNumber] = column_id.split('.') + return ` +select * +from pg_attribute a +where a.attrelid = ${literal(relationId)} + and a.attnum = ${literal(columnNumber)} +into col; +execute format( + 'revoke ${privilege_type} (%I) on %s from ${ + grantee.toLowerCase() === 'public' ? 'public' : ident(grantee) + }', + col.attname, + col.attrelid::regclass +);` + }) + .join('\n')} +end $$; +` + const { data, error } = await this.query(sql) + if (error) { + return { data, error } + } + + // Return the updated column privileges for modified columns. + const columnIds = [...new Set(revokes.map(({ column_id }) => column_id))] + const columnIdsFilter = filterByValue(columnIds) + sql = COLUMN_PRIVILEGES_SQL({ columnIdsFilter }) + return await this.query(sql) + } +} diff --git a/src/lib/PostgresMetaColumns.ts b/src/lib/PostgresMetaColumns.ts index 0a10b815..613c8ea2 100644 --- a/src/lib/PostgresMetaColumns.ts +++ b/src/lib/PostgresMetaColumns.ts @@ -1,8 +1,9 @@ import { ident, literal } from 'pg-format' -import PostgresMetaTables from './PostgresMetaTables' -import { DEFAULT_SYSTEM_SCHEMAS } from './constants' -import { columnsSql } from './sql' -import { PostgresMetaResult, PostgresColumn } from './types' +import PostgresMetaTables from './PostgresMetaTables.js' +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' +import { PostgresMetaResult, PostgresColumn } from './types.js' +import { filterByValue, filterByList } from './helpers.js' +import { COLUMNS_SQL } from './sql/columns.sql.js' export default class PostgresMetaColumns { query: (sql: string) => Promise> @@ -13,10 +14,28 @@ export default class PostgresMetaColumns { this.metaTables = new PostgresMetaTables(query) } - async list({ includeSystemSchemas = false } = {}): Promise> { - const sql = includeSystemSchemas - ? columnsSql - : `${columnsSql} AND NOT (nc.nspname IN (${DEFAULT_SYSTEM_SCHEMAS.map(literal).join(',')}));` + async list({ + tableId, + includeSystemSchemas = false, + includedSchemas, + excludedSchemas, + limit, + offset, + }: { + tableId?: number + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + } = {}): Promise> { + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + const tableIdFilter = tableId ? filterByValue([`${tableId}`]) : undefined + const sql = COLUMNS_SQL({ schemaFilter, tableIdFilter, limit, offset }) return await this.query(sql) } @@ -41,6 +60,7 @@ export default class PostgresMetaColumns { table?: string schema?: string }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { const regexp = /^(\d+)\.(\d+)$/ if (!regexp.test(id)) { @@ -48,7 +68,8 @@ export default class PostgresMetaColumns { } const matches = id.match(regexp) as RegExpMatchArray const [tableId, ordinalPos] = matches.slice(1).map(Number) - const sql = `${columnsSql} AND c.oid = ${tableId} AND a.attnum = ${ordinalPos};` + const idsFilter = filterByValue([`${tableId}.${ordinalPos}`]) + const sql = COLUMNS_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -58,9 +79,8 @@ export default class PostgresMetaColumns { return { data: data[0], error } } } else if (name && table) { - const sql = `${columnsSql} AND a.attname = ${literal(name)} AND c.relname = ${literal( - table - )} AND nc.nspname = ${literal(schema)};` + const columnNameFilter = filterByValue([`${table}.${name}`]) + const sql = `${COLUMNS_SQL({ schemaFilter, columnNameFilter })};` const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -111,15 +131,26 @@ export default class PostgresMetaColumns { } const { name: table, schema } = data! - let defaultValueClause: string - if (default_value === undefined) { - defaultValueClause = '' - } else if (default_value_format === 'expression') { - defaultValueClause = `DEFAULT ${default_value}` + let defaultValueClause = '' + if (is_identity) { + if (default_value !== undefined) { + return { + data: null, + error: { message: 'Columns cannot both be identity and have a default value' }, + } + } + + defaultValueClause = `GENERATED ${identity_generation} AS IDENTITY` } else { - defaultValueClause = `DEFAULT ${literal(default_value)}` + if (default_value === undefined) { + // skip + } else if (default_value_format === 'expression') { + defaultValueClause = `DEFAULT ${default_value}` + } else { + defaultValueClause = `DEFAULT ${literal(default_value)}` + } } - const isIdentityClause = is_identity ? `GENERATED ${identity_generation} AS IDENTITY` : '' + let isNullableClause = '' if (is_nullable !== undefined) { isNullableClause = is_nullable ? 'NULL' : 'NOT NULL' @@ -134,9 +165,8 @@ export default class PostgresMetaColumns { const sql = ` BEGIN; - ALTER TABLE ${ident(schema)}.${ident(table)} ADD COLUMN ${ident(name)} ${type} + ALTER TABLE ${ident(schema)}.${ident(table)} ADD COLUMN ${ident(name)} ${typeIdent(type)} ${defaultValueClause} - ${isIdentityClause} ${isNullableClause} ${isPrimaryKeyClause} ${isUniqueClause} @@ -161,9 +191,11 @@ COMMIT;` default_value, default_value_format = 'literal', is_identity, - identity_generation, + identity_generation = 'BY DEFAULT', is_nullable, + is_unique, comment, + check, }: { name?: string type?: string @@ -173,7 +205,9 @@ COMMIT;` is_identity?: boolean identity_generation?: 'BY DEFAULT' | 'ALWAYS' is_nullable?: boolean + is_unique?: boolean comment?: string + check?: string | null } ): Promise> { const { data: old, error } = await this.retrieve({ id }) @@ -193,7 +227,7 @@ COMMIT;` ? '' : `ALTER TABLE ${ident(old!.schema)}.${ident(old!.table)} ALTER COLUMN ${ident( old!.name - )} SET DATA TYPE ${type} USING ${ident(old!.name)}::${type};` + )} SET DATA TYPE ${typeIdent(type)} USING ${ident(old!.name)}::${typeIdent(type)};` let defaultValueSql: string if (drop_default) { @@ -245,6 +279,32 @@ COMMIT;` old!.name )} SET NOT NULL;` } + let isUniqueSql = '' + if (old!.is_unique === true && is_unique === false) { + isUniqueSql = ` +DO $$ +DECLARE + r record; +BEGIN + FOR r IN + SELECT conname FROM pg_constraint WHERE + contype = 'u' + AND cardinality(conkey) = 1 + AND conrelid = ${literal(old!.table_id)} + AND conkey[1] = ${literal(old!.ordinal_position)} + LOOP + EXECUTE ${literal( + `ALTER TABLE ${ident(old!.schema)}.${ident(old!.table)} DROP CONSTRAINT ` + )} || quote_ident(r.conname); + END LOOP; +END +$$; +` + } else if (old!.is_unique === false && is_unique === true) { + isUniqueSql = `ALTER TABLE ${ident(old!.schema)}.${ident(old!.table)} ADD UNIQUE (${ident( + old!.name + )});` + } const commentSql = comment === undefined ? '' @@ -252,17 +312,66 @@ COMMIT;` old!.name )} IS ${literal(comment)};` - // nameSql must be last. - // defaultValueSql must be after typeSql. - // TODO: Can't set default if column is previously identity even if is_identity: false. - // Must do two separate PATCHes (once to drop identity and another to set default). + const checkSql = + check === undefined + ? '' + : ` +DO $$ +DECLARE + v_conname name; + v_conkey int2[]; +BEGIN + SELECT conname into v_conname FROM pg_constraint WHERE + contype = 'c' + AND cardinality(conkey) = 1 + AND conrelid = ${literal(old!.table_id)} + AND conkey[1] = ${literal(old!.ordinal_position)} + ORDER BY oid asc + LIMIT 1; + + IF v_conname IS NOT NULL THEN + EXECUTE format('ALTER TABLE ${ident(old!.schema)}.${ident( + old!.table + )} DROP CONSTRAINT %s', v_conname); + END IF; + + ${ + check !== null + ? ` + ALTER TABLE ${ident(old!.schema)}.${ident(old!.table)} ADD CONSTRAINT ${ident( + `${old!.table}_${old!.name}_check` + )} CHECK (${check}); + + SELECT conkey into v_conkey FROM pg_constraint WHERE conname = ${literal( + `${old!.table}_${old!.name}_check` + )}; + + ASSERT v_conkey IS NOT NULL, 'error creating column constraint: check condition must refer to this column'; + ASSERT cardinality(v_conkey) = 1, 'error creating column constraint: check condition cannot refer to multiple columns'; + ASSERT v_conkey[1] = ${literal( + old!.ordinal_position + )}, 'error creating column constraint: check condition cannot refer to other columns'; + ` + : '' + } +END +$$; +` + + // TODO: Can't set default if column is previously identity even if + // is_identity: false. Must do two separate PATCHes (once to drop identity + // and another to set default). + // NOTE: nameSql must be last. defaultValueSql must be after typeSql. + // identitySql must be after isNullableSql. const sql = ` BEGIN; ${isNullableSql} ${typeSql} ${defaultValueSql} ${identitySql} + ${isUniqueSql} ${commentSql} + ${checkSql} ${nameSql} COMMIT;` { @@ -274,14 +383,14 @@ COMMIT;` return await this.retrieve({ id }) } - async remove(id: string): Promise> { + async remove(id: string, { cascade = false } = {}): Promise> { const { data: column, error } = await this.retrieve({ id }) if (error) { return { data: null, error } } const sql = `ALTER TABLE ${ident(column!.schema)}.${ident(column!.table)} DROP COLUMN ${ident( column!.name - )};` + )} ${cascade ? 'CASCADE' : 'RESTRICT'};` { const { error } = await this.query(sql) if (error) { @@ -291,3 +400,13 @@ COMMIT;` return { data: column!, error: null } } } + +// TODO: make this more robust - use type_id or type_schema + type_name instead +// of just type. +const typeIdent = (type: string) => { + return type.endsWith('[]') + ? `${ident(type.slice(0, -2))}[]` + : type.includes('.') + ? type + : ident(type) +} diff --git a/src/lib/PostgresMetaConfig.ts b/src/lib/PostgresMetaConfig.ts index f31a140a..35b194d8 100644 --- a/src/lib/PostgresMetaConfig.ts +++ b/src/lib/PostgresMetaConfig.ts @@ -1,5 +1,5 @@ -import { configSql } from './sql' -import { PostgresMetaResult, PostgresConfig } from './types' +import { CONFIG_SQL } from './sql/config.sql.js' +import { PostgresMetaResult, PostgresConfig } from './types.js' export default class PostgresMetaConfig { query: (sql: string) => Promise> @@ -8,7 +8,14 @@ export default class PostgresMetaConfig { this.query = query } - async list(): Promise> { - return await this.query(configSql) + async list({ + limit, + offset, + }: { + limit?: number + offset?: number + } = {}): Promise> { + const sql = CONFIG_SQL({ limit, offset }) + return await this.query(sql) } } diff --git a/src/lib/PostgresMetaExtensions.ts b/src/lib/PostgresMetaExtensions.ts index 0fb27164..9543dc2c 100644 --- a/src/lib/PostgresMetaExtensions.ts +++ b/src/lib/PostgresMetaExtensions.ts @@ -1,6 +1,7 @@ import { ident, literal } from 'pg-format' -import { extensionsSql } from './sql' -import { PostgresMetaResult, PostgresExtension } from './types' +import { PostgresMetaResult, PostgresExtension } from './types.js' +import { EXTENSIONS_SQL } from './sql/extensions.sql.js' +import { filterByValue } from './helpers.js' export default class PostgresMetaExtensions { query: (sql: string) => Promise> @@ -9,12 +10,20 @@ export default class PostgresMetaExtensions { this.query = query } - async list(): Promise> { - return await this.query(extensionsSql) + async list({ + limit, + offset, + }: { + limit?: number + offset?: number + } = {}): Promise> { + const sql = EXTENSIONS_SQL({ limit, offset }) + return await this.query(sql) } async retrieve({ name }: { name: string }): Promise> { - const sql = `${extensionsSql} WHERE name = ${literal(name)};` + const nameFilter = filterByValue([name]) + const sql = EXTENSIONS_SQL({ nameFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } diff --git a/src/lib/PostgresMetaForeignTables.ts b/src/lib/PostgresMetaForeignTables.ts new file mode 100644 index 00000000..e565da43 --- /dev/null +++ b/src/lib/PostgresMetaForeignTables.ts @@ -0,0 +1,123 @@ +import { coalesceRowsToArray, filterByList, filterByValue } from './helpers.js' +import { PostgresMetaResult, PostgresForeignTable } from './types.js' +import { FOREIGN_TABLES_SQL } from './sql/foreign_tables.sql.js' +import { COLUMNS_SQL } from './sql/columns.sql.js' + +export default class PostgresMetaForeignTables { + query: (sql: string) => Promise> + + constructor(query: (sql: string) => Promise>) { + this.query = query + } + + async list(options: { + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + includeColumns: false + }): Promise> + async list(options?: { + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + includeColumns?: boolean + }): Promise> + async list({ + includedSchemas, + excludedSchemas, + limit, + offset, + includeColumns = true, + }: { + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + includeColumns?: boolean + } = {}): Promise> { + const schemaFilter = filterByList(includedSchemas, excludedSchemas) + const sql = generateEnrichedForeignTablesSql({ includeColumns, schemaFilter, limit, offset }) + return await this.query(sql) + } + + async retrieve({ id }: { id: number }): Promise> + async retrieve({ + name, + schema, + }: { + name: string + schema: string + }): Promise> + async retrieve({ + id, + name, + schema = 'public', + }: { + id?: number + name?: string + schema?: string + }): Promise> { + if (id) { + const idsFilter = filterByValue([`${id}`]) + const sql = generateEnrichedForeignTablesSql({ + includeColumns: true, + idsFilter, + }) + const { data, error } = await this.query(sql) + if (error) { + return { data, error } + } else if (data.length === 0) { + return { data: null, error: { message: `Cannot find a foreign table with ID ${id}` } } + } else { + return { data: data[0], error } + } + } else if (name) { + const nameFilter = filterByValue([`${schema}.${name}`]) + const sql = generateEnrichedForeignTablesSql({ + includeColumns: true, + tableIdentifierFilter: nameFilter, + }) + const { data, error } = await this.query(sql) + if (error) { + return { data, error } + } else if (data.length === 0) { + return { + data: null, + error: { message: `Cannot find a foreign table named ${name} in schema ${schema}` }, + } + } else { + return { data: data[0], error } + } + } else { + return { data: null, error: { message: 'Invalid parameters on foreign table retrieve' } } + } + } +} + +const generateEnrichedForeignTablesSql = ({ + includeColumns, + schemaFilter, + idsFilter, + tableIdentifierFilter, + limit, + offset, +}: { + includeColumns: boolean + schemaFilter?: string + idsFilter?: string + tableIdentifierFilter?: string + limit?: number + offset?: number +}) => ` +with foreign_tables as (${FOREIGN_TABLES_SQL({ schemaFilter, tableIdentifierFilter, limit, offset })}) + ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, tableIdentifierFilter, tableIdFilter: idsFilter })})` : ''} +select + * + ${ + includeColumns + ? `, ${coalesceRowsToArray('columns', 'columns.table_id = foreign_tables.id')}` + : '' + } +from foreign_tables` diff --git a/src/lib/PostgresMetaFunctions.ts b/src/lib/PostgresMetaFunctions.ts index abf6be38..b6e2a39c 100644 --- a/src/lib/PostgresMetaFunctions.ts +++ b/src/lib/PostgresMetaFunctions.ts @@ -1,7 +1,8 @@ import { ident, literal } from 'pg-format' -import { DEFAULT_SYSTEM_SCHEMAS } from './constants' -import { functionsSql } from './sql' -import { PostgresMetaResult, PostgresFunction } from './types' +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' +import { filterByList, filterByValue } from './helpers.js' +import { PostgresMetaResult, PostgresFunction, PostgresFunctionCreate } from './types.js' +import { FUNCTIONS_SQL } from './sql/functions.sql.js' export default class PostgresMetaFunctions { query: (sql: string) => Promise> @@ -10,14 +11,25 @@ export default class PostgresMetaFunctions { this.query = query } - async list({ includeSystemSchemas = false } = {}): Promise< - PostgresMetaResult - > { - const sql = includeSystemSchemas - ? enrichedFunctionsSql - : `${enrichedFunctionsSql} WHERE NOT (schema IN (${DEFAULT_SYSTEM_SCHEMAS.map(literal).join( - ',' - )}));` + async list({ + includeSystemSchemas = false, + includedSchemas, + excludedSchemas, + limit, + offset, + }: { + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + } = {}): Promise> { + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + const sql = FUNCTIONS_SQL({ schemaFilter, limit, offset }) return await this.query(sql) } @@ -42,8 +54,10 @@ export default class PostgresMetaFunctions { schema?: string args?: string[] }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const sql = `${enrichedFunctionsSql} WHERE id = ${literal(id)};` + const idsFilter = filterByValue([id]) + const sql = FUNCTIONS_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -53,10 +67,8 @@ export default class PostgresMetaFunctions { return { data: data[0], error } } } else if (name && schema && args) { - const argTypes = args.join(', ') - const sql = `${enrichedFunctionsSql} WHERE schema = ${literal(schema)} AND name = ${literal( - name - )} AND argument_types = ${literal(argTypes)};` + const nameFilter = filterByValue([name]) + const sql = FUNCTIONS_SQL({ schemaFilter, nameFilter, args: args.map(literal) }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -64,7 +76,7 @@ export default class PostgresMetaFunctions { return { data: null, error: { - message: `Cannot find function "${schema}"."${name}"(${argTypes})`, + message: `Cannot find function "${schema}"."${name}"(${args.join(', ')})`, }, } } else { @@ -80,23 +92,23 @@ export default class PostgresMetaFunctions { schema = 'public', args = [], definition, - rettype = 'void', + return_type = 'void', language = 'sql', - }: { - name: string - schema?: string - args?: string[] - definition: string - rettype?: string - language?: string - }): Promise> { - const sql = ` - CREATE FUNCTION ${ident(schema)}.${ident(name)}(${args.join(', ')}) - RETURNS ${rettype} - AS ${literal(definition)} - LANGUAGE ${language} - RETURNS NULL ON NULL INPUT; - ` + behavior = 'VOLATILE', + security_definer = false, + config_params = {}, + }: PostgresFunctionCreate): Promise> { + const sql = this.generateCreateFunctionSql({ + name, + schema, + args, + definition, + return_type, + language, + behavior, + security_definer, + config_params, + }) const { error } = await this.query(sql) if (error) { return { data: null, error } @@ -109,36 +121,87 @@ export default class PostgresMetaFunctions { { name, schema, + definition, }: { name?: string schema?: string + definition?: string } ): Promise> { - const { data: old, error: retrieveError } = await this.retrieve({ id }) - if (retrieveError) { - return { data: null, error: retrieveError } + const { data: currentFunc, error } = await this.retrieve({ id }) + if (error) { + return { data: null, error } } + const args = currentFunc!.argument_types.split(', ') + const identityArgs = currentFunc!.identity_argument_types + + const updateDefinitionSql = + typeof definition === 'string' + ? this.generateCreateFunctionSql( + { + ...currentFunc!, + definition, + args, + config_params: currentFunc!.config_params ?? {}, + }, + { replace: true } + ) + : '' + const updateNameSql = - name && name !== old!.name - ? `ALTER FUNCTION ${ident(old!.schema)}.${ident(old!.name)}(${ - old!.argument_types - }) RENAME TO ${ident(name)};` + name && name !== currentFunc!.name + ? `ALTER FUNCTION ${ident(currentFunc!.schema)}.${ident( + currentFunc!.name + )}(${identityArgs}) RENAME TO ${ident(name)};` : '' const updateSchemaSql = - schema && schema !== old!.schema - ? `ALTER FUNCTION ${ident(old!.schema)}.${ident(name || old!.name)}(${ - old!.argument_types - }) SET SCHEMA ${ident(schema)};` + schema && schema !== currentFunc!.schema + ? `ALTER FUNCTION ${ident(currentFunc!.schema)}.${ident( + name || currentFunc!.name + )}(${identityArgs}) SET SCHEMA ${ident(schema)};` : '' - const sql = `BEGIN; ${updateNameSql} ${updateSchemaSql} COMMIT;` + const currentSchemaFilter = currentFunc!.schema + ? filterByList([currentFunc!.schema], []) + : undefined + const currentNameFilter = currentFunc!.name ? filterByValue([currentFunc!.name]) : undefined - const { error } = await this.query(sql) - if (error) { - return { data: null, error } + const sql = ` + DO LANGUAGE plpgsql $$ + BEGIN + IF ${typeof definition === 'string' ? 'TRUE' : 'FALSE'} THEN + ${updateDefinitionSql} + + IF ( + SELECT id + FROM (${FUNCTIONS_SQL({ schemaFilter: currentSchemaFilter, nameFilter: currentNameFilter })}) AS f + WHERE f.schema = ${literal(currentFunc!.schema)} + AND f.name = ${literal(currentFunc!.name)} + AND f.identity_argument_types = ${literal(identityArgs)} + ) != ${id} THEN + RAISE EXCEPTION 'Cannot find function "${currentFunc!.schema}"."${ + currentFunc!.name + }"(${identityArgs})'; + END IF; + END IF; + + ${updateNameSql} + + ${updateSchemaSql} + END; + $$; + ` + + { + const { error } = await this.query(sql) + + if (error) { + return { data: null, error } + } } + return await this.retrieve({ id }) } @@ -151,7 +214,7 @@ export default class PostgresMetaFunctions { return { data: null, error } } const sql = `DROP FUNCTION ${ident(func!.schema)}.${ident(func!.name)} - (${func!.argument_types}) + (${func!.identity_argument_types}) ${cascade ? 'CASCADE' : 'RESTRICT'};` { const { error } = await this.query(sql) @@ -161,13 +224,41 @@ export default class PostgresMetaFunctions { } return { data: func!, error: null } } -} -const enrichedFunctionsSql = ` - WITH functions AS ( - ${functionsSql} - ) - SELECT - * - FROM functions -` + private generateCreateFunctionSql( + { + name, + schema, + args, + definition, + return_type, + language, + behavior, + security_definer, + config_params, + }: PostgresFunctionCreate, + { replace = false } = {} + ): string { + return ` + CREATE ${replace ? 'OR REPLACE' : ''} FUNCTION ${ident(schema!)}.${ident(name!)}(${ + args?.join(', ') || '' + }) + RETURNS ${return_type} + AS ${literal(definition)} + LANGUAGE ${language} + ${behavior} + CALLED ON NULL INPUT + ${security_definer ? 'SECURITY DEFINER' : 'SECURITY INVOKER'} + ${ + config_params + ? Object.entries(config_params) + .map( + ([param, value]: string[]) => + `SET ${param} ${value[0] === 'FROM CURRENT' ? 'FROM CURRENT' : 'TO ' + value}` + ) + .join('\n') + : '' + }; + ` + } +} diff --git a/src/lib/PostgresMetaIndexes.ts b/src/lib/PostgresMetaIndexes.ts new file mode 100644 index 00000000..84f7f100 --- /dev/null +++ b/src/lib/PostgresMetaIndexes.ts @@ -0,0 +1,66 @@ +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' +import { filterByList, filterByValue } from './helpers.js' +import { PostgresMetaResult, PostgresIndex } from './types.js' +import { INDEXES_SQL } from './sql/indexes.sql.js' + +export default class PostgresMetaIndexes { + query: (sql: string) => Promise> + + constructor(query: (sql: string) => Promise>) { + this.query = query + } + + async list({ + includeSystemSchemas = false, + includedSchemas, + excludedSchemas, + limit, + offset, + }: { + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + } = {}): Promise> { + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + const sql = INDEXES_SQL({ schemaFilter, limit, offset }) + return await this.query(sql) + } + + async retrieve({ id }: { id: number }): Promise> + async retrieve({ + name, + schema, + args, + }: { + name: string + schema: string + args: string[] + }): Promise> + async retrieve({ + id, + }: { + id?: number + args?: string[] + }): Promise> { + if (id) { + const idsFilter = filterByValue([id]) + const sql = INDEXES_SQL({ idsFilter }) + const { data, error } = await this.query(sql) + if (error) { + return { data, error } + } else if (data.length === 0) { + return { data: null, error: { message: `Cannot find a index with ID ${id}` } } + } else { + return { data: data[0], error } + } + } else { + return { data: null, error: { message: 'Invalid parameters on function retrieve' } } + } + } +} diff --git a/src/lib/PostgresMetaMaterializedViews.ts b/src/lib/PostgresMetaMaterializedViews.ts new file mode 100644 index 00000000..0a32793a --- /dev/null +++ b/src/lib/PostgresMetaMaterializedViews.ts @@ -0,0 +1,109 @@ +import { filterByList, coalesceRowsToArray, filterByValue } from './helpers.js' +import { PostgresMetaResult, PostgresMaterializedView } from './types.js' +import { MATERIALIZED_VIEWS_SQL } from './sql/materialized_views.sql.js' +import { COLUMNS_SQL } from './sql/columns.sql.js' + +export default class PostgresMetaMaterializedViews { + query: (sql: string) => Promise> + + constructor(query: (sql: string) => Promise>) { + this.query = query + } + + async list({ + includedSchemas, + excludedSchemas, + limit, + offset, + includeColumns = false, + }: { + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + includeColumns?: boolean + } = {}): Promise> { + const schemaFilter = filterByList(includedSchemas, excludedSchemas, undefined) + let sql = generateEnrichedMaterializedViewsSql({ includeColumns, schemaFilter, limit, offset }) + return await this.query(sql) + } + + async retrieve({ id }: { id: number }): Promise> + async retrieve({ + name, + schema, + }: { + name: string + schema: string + }): Promise> + async retrieve({ + id, + name, + schema = 'public', + }: { + id?: number + name?: string + schema?: string + }): Promise> { + if (id) { + const idsFilter = filterByValue([id]) + const sql = generateEnrichedMaterializedViewsSql({ + includeColumns: true, + idsFilter, + }) + const { data, error } = await this.query(sql) + if (error) { + return { data, error } + } else if (data.length === 0) { + return { data: null, error: { message: `Cannot find a materialized view with ID ${id}` } } + } else { + return { data: data[0], error } + } + } else if (name) { + const materializedViewIdentifierFilter = filterByValue([`${schema}.${name}`]) + const sql = generateEnrichedMaterializedViewsSql({ + includeColumns: true, + materializedViewIdentifierFilter, + }) + const { data, error } = await this.query(sql) + if (error) { + return { data, error } + } else if (data.length === 0) { + return { + data: null, + error: { message: `Cannot find a materialized view named ${name} in schema ${schema}` }, + } + } else { + return { data: data[0], error } + } + } else { + return { data: null, error: { message: 'Invalid parameters on materialized view retrieve' } } + } + } +} + +const generateEnrichedMaterializedViewsSql = ({ + includeColumns, + schemaFilter, + materializedViewIdentifierFilter, + idsFilter, + limit, + offset, +}: { + includeColumns: boolean + schemaFilter?: string + materializedViewIdentifierFilter?: string + idsFilter?: string + limit?: number + offset?: number +}) => ` +with materialized_views as (${MATERIALIZED_VIEWS_SQL({ schemaFilter, limit, offset, materializedViewIdentifierFilter, idsFilter })}) + ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, limit, offset, tableIdentifierFilter: materializedViewIdentifierFilter, tableIdFilter: idsFilter })})` : ''} +select + * + ${ + includeColumns + ? `, ${coalesceRowsToArray('columns', 'columns.table_id = materialized_views.id')}` + : '' + } +from materialized_views` diff --git a/src/lib/PostgresMetaPolicies.ts b/src/lib/PostgresMetaPolicies.ts index 51474435..72d3157b 100644 --- a/src/lib/PostgresMetaPolicies.ts +++ b/src/lib/PostgresMetaPolicies.ts @@ -1,7 +1,8 @@ -import { ident, literal } from 'pg-format' -import { DEFAULT_SYSTEM_SCHEMAS } from './constants' -import { policiesSql } from './sql' -import { PostgresMetaResult, PostgresPolicy } from './types' +import { ident } from 'pg-format' +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' +import { filterByList, filterByValue } from './helpers.js' +import { PostgresMetaResult, PostgresPolicy } from './types.js' +import { POLICIES_SQL } from './sql/policies.sql.js' export default class PostgresMetaPolicies { query: (sql: string) => Promise> @@ -10,12 +11,25 @@ export default class PostgresMetaPolicies { this.query = query } - async list({ includeSystemSchemas = false } = {}): Promise> { - const sql = includeSystemSchemas - ? policiesSql - : `${policiesSql} WHERE NOT (n.nspname IN (${DEFAULT_SYSTEM_SCHEMAS.map(literal).join( - ',' - )}));` + async list({ + includeSystemSchemas = false, + includedSchemas, + excludedSchemas, + limit, + offset, + }: { + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + } = {}): Promise> { + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + let sql = POLICIES_SQL({ schemaFilter, limit, offset }) return await this.query(sql) } @@ -40,8 +54,10 @@ export default class PostgresMetaPolicies { table?: string schema?: string }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const sql = `${policiesSql} WHERE pol.oid = ${literal(id)};` + const idsFilter = filterByValue([`${id}`]) + const sql = POLICIES_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -51,9 +67,8 @@ export default class PostgresMetaPolicies { return { data: data[0], error } } } else if (name && table) { - const sql = `${policiesSql} WHERE pol.polname = ${literal(name)} AND n.nspname = ${literal( - schema - )} AND c.relname = ${literal(table)};` + const functionNameIdentifierFilter = filterByValue([`${table}.${name}`]) + const sql = POLICIES_SQL({ schemaFilter, functionNameIdentifierFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -78,7 +93,7 @@ export default class PostgresMetaPolicies { check, action = 'PERMISSIVE', command = 'ALL', - roles = ['PUBLIC'], + roles = ['public'], }: { name: string table: string @@ -95,7 +110,7 @@ export default class PostgresMetaPolicies { CREATE POLICY ${ident(name)} ON ${ident(schema)}.${ident(table)} AS ${action} FOR ${command} - TO ${roles.join(',')} + TO ${roles.map(ident).join(',')} ${definitionClause} ${checkClause};` const { error } = await this.query(sql) if (error) { @@ -127,7 +142,7 @@ CREATE POLICY ${ident(name)} ON ${ident(schema)}.${ident(table)} const nameSql = name === undefined ? '' : `${alter} RENAME TO ${ident(name)};` const definitionSql = definition === undefined ? '' : `${alter} USING (${definition});` const checkSql = check === undefined ? '' : `${alter} WITH CHECK (${check});` - const rolesSql = roles === undefined ? '' : `${alter} TO (${roles.join(',')});` + const rolesSql = roles === undefined ? '' : `${alter} TO ${roles.map(ident).join(',')};` // nameSql must be last const sql = `BEGIN; ${definitionSql} ${checkSql} ${rolesSql} ${nameSql} COMMIT;` diff --git a/src/lib/PostgresMetaPublications.ts b/src/lib/PostgresMetaPublications.ts index 9890e1fe..f3fdc549 100644 --- a/src/lib/PostgresMetaPublications.ts +++ b/src/lib/PostgresMetaPublications.ts @@ -1,6 +1,7 @@ import { ident, literal } from 'pg-format' -import { publicationsSql } from './sql' -import { PostgresMetaResult, PostgresPublication } from './types' +import { PostgresMetaResult, PostgresPublication } from './types.js' +import { PUBLICATIONS_SQL } from './sql/publications.sql.js' +import { filterByValue } from './helpers.js' export default class PostgresMetaPublications { query: (sql: string) => Promise> @@ -9,8 +10,15 @@ export default class PostgresMetaPublications { this.query = query } - async list(): Promise> { - return await this.query(publicationsSql) + async list({ + limit, + offset, + }: { + limit?: number + offset?: number + }): Promise> { + let sql = PUBLICATIONS_SQL({ limit, offset }) + return await this.query(sql) } async retrieve({ id }: { id: number }): Promise> @@ -23,7 +31,8 @@ export default class PostgresMetaPublications { name?: string }): Promise> { if (id) { - const sql = `${publicationsSql} WHERE p.oid = ${literal(id)};` + const idsFilter = filterByValue([id]) + const sql = PUBLICATIONS_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -33,7 +42,8 @@ export default class PostgresMetaPublications { return { data: data[0], error } } } else if (name) { - const sql = `${publicationsSql} WHERE p.pubname = ${literal(name)};` + const nameFilter = filterByValue([name]) + const sql = PUBLICATIONS_SQL({ nameFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -53,17 +63,17 @@ export default class PostgresMetaPublications { publish_update = false, publish_delete = false, publish_truncate = false, - tables, + tables = null, }: { name: string publish_insert?: boolean publish_update?: boolean publish_delete?: boolean publish_truncate?: boolean - tables?: string[] + tables?: string[] | null }): Promise> { let tableClause: string - if (tables === undefined) { + if (tables === undefined || tables === null) { tableClause = 'FOR ALL TABLES' } else if (tables.length === 0) { tableClause = '' @@ -114,85 +124,109 @@ CREATE PUBLICATION ${ident(name)} ${tableClause} publish_update?: boolean publish_delete?: boolean publish_truncate?: boolean - tables?: string[] + tables?: string[] | null } ): Promise> { - const { data: old, error } = await this.retrieve({ id }) + const sql = ` +do $$ +declare + id oid := ${literal(id)}; + old record; + new_name text := ${name === undefined ? null : literal(name)}; + new_owner text := ${owner === undefined ? null : literal(owner)}; + new_publish_insert bool := ${publish_insert ?? null}; + new_publish_update bool := ${publish_update ?? null}; + new_publish_delete bool := ${publish_delete ?? null}; + new_publish_truncate bool := ${publish_truncate ?? null}; + new_tables text := ${ + tables === undefined + ? null + : literal( + tables === null + ? 'all tables' + : tables + .map((t) => { + if (!t.includes('.')) { + return ident(t) + } + + const [schema, ...rest] = t.split('.') + const table = rest.join('.') + return `${ident(schema)}.${ident(table)}` + }) + .join(',') + ) + }; +begin + select * into old from pg_publication where oid = id; + if old is null then + raise exception 'Cannot find publication with id %', id; + end if; + + if new_tables is null then + null; + elsif new_tables = 'all tables' then + if old.puballtables then + null; + else + -- Need to recreate because going from list of tables <-> all tables with alter is not possible. + execute(format('drop publication %1$I; create publication %1$I for all tables;', old.pubname)); + end if; + else + if old.puballtables then + -- Need to recreate because going from list of tables <-> all tables with alter is not possible. + execute(format('drop publication %1$I; create publication %1$I;', old.pubname)); + elsif exists(select from pg_publication_rel where prpubid = id) then + execute( + format( + 'alter publication %I drop table %s', + old.pubname, + (select string_agg(prrelid::regclass::text, ', ') from pg_publication_rel where prpubid = id) + ) + ); + end if; + + -- At this point the publication must have no tables. + + if new_tables != '' then + execute(format('alter publication %I add table %s', old.pubname, new_tables)); + end if; + end if; + + execute( + format( + 'alter publication %I set (publish = %L);', + old.pubname, + concat_ws( + ', ', + case when coalesce(new_publish_insert, old.pubinsert) then 'insert' end, + case when coalesce(new_publish_update, old.pubupdate) then 'update' end, + case when coalesce(new_publish_delete, old.pubdelete) then 'delete' end, + case when coalesce(new_publish_truncate, old.pubtruncate) then 'truncate' end + ) + ) + ); + + execute(format('alter publication %I owner to %I;', old.pubname, coalesce(new_owner, old.pubowner::regrole::name))); + + -- Using the same name in the rename clause gives an error, so only do it if the new name is different. + if new_name is not null and new_name != old.pubname then + execute(format('alter publication %I rename to %I;', old.pubname, coalesce(new_name, old.pubname))); + end if; + + -- We need to retrieve the publication later, so we need a way to uniquely identify which publication this is. + -- We can't rely on id because it gets changed if it got recreated. + -- We use a temp table to store the unique name - DO blocks can't return a value. + create temp table pg_meta_publication_tmp (name) on commit drop as values (coalesce(new_name, old.pubname)); +end $$; + +with publications as (${PUBLICATIONS_SQL({})}) select * from publications where name = (select name from pg_meta_publication_tmp); +` + const { data, error } = await this.query(sql) if (error) { return { data: null, error } } - - // Need to work around the limitations of the SQL. Can't add/drop tables from - // a publication with FOR ALL TABLES. Can't use the SET TABLE clause without - // at least one table. - // - // new tables - // - // | undefined | string[] | - // ---------|-----------|-----------------| - // null | '' | 400 Bad Request | - // old tables ---------|-----------|-----------------| - // object[] | '' | See below | - // - // new tables - // - // | [] | [...] | - // ---------|-----------|-----------------| - // [] | '' | SET TABLE | - // old tables ---------|-----------|-----------------| - // [...] | DROP all | SET TABLE | - // - let tableSql: string - if (tables === undefined) { - tableSql = '' - } else if (old!.tables === null) { - throw new Error('Tables cannot be added to or dropped from FOR ALL TABLES publications') - } else if (tables.length > 0) { - tableSql = `ALTER PUBLICATION ${ident(old!.name)} SET TABLE ${tables - .map((t) => { - if (!t.includes('.')) { - return ident(t) - } - - const [schema, ...rest] = t.split('.') - const table = rest.join('.') - return `${ident(schema)}.${ident(table)}` - }) - .join(',')};` - } else if (old!.tables.length === 0) { - tableSql = '' - } else { - tableSql = `ALTER PUBLICATION ${ident(old!.name)} DROP TABLE ${old!.tables - .map((table) => `${ident(table.schema)}.${ident(table.name)}`) - .join(',')};` - } - - let publishOps = [] - if (publish_insert ?? old!.publish_insert) publishOps.push('insert') - if (publish_update ?? old!.publish_update) publishOps.push('update') - if (publish_delete ?? old!.publish_delete) publishOps.push('delete') - if (publish_truncate ?? old!.publish_truncate) publishOps.push('truncate') - const publishSql = `ALTER PUBLICATION ${ident(old!.name)} SET (publish = '${publishOps.join( - ',' - )}');` - - const ownerSql = - owner === undefined ? '' : `ALTER PUBLICATION ${ident(old!.name)} OWNER TO ${ident(owner)};` - - const nameSql = - name === undefined || name === old!.name - ? '' - : `ALTER PUBLICATION ${ident(old!.name)} RENAME TO ${ident(name)};` - - // nameSql must be last - const sql = `BEGIN; ${tableSql} ${publishSql} ${ownerSql} ${nameSql} COMMIT;` - { - const { error } = await this.query(sql) - if (error) { - return { data: null, error } - } - } - return await this.retrieve({ id }) + return { data: data[0], error } } async remove(id: number): Promise> { diff --git a/src/lib/PostgresMetaRelationships.ts b/src/lib/PostgresMetaRelationships.ts new file mode 100644 index 00000000..e4e47d60 --- /dev/null +++ b/src/lib/PostgresMetaRelationships.ts @@ -0,0 +1,164 @@ +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' +import { filterByList } from './helpers.js' +import type { PostgresMetaResult, PostgresRelationship } from './types.js' +import { TABLE_RELATIONSHIPS_SQL } from './sql/table_relationships.sql.js' +import { VIEWS_KEY_DEPENDENCIES_SQL } from './sql/views_key_dependencies.sql.js' + +/* + * Only used for generating types at the moment. Will need some cleanups before + * using it for other things, e.g. /relationships endpoint. + */ +export default class PostgresMetaRelationships { + query: (sql: string) => Promise> + + constructor(query: (sql: string) => Promise>) { + this.query = query + } + + async list({ + includeSystemSchemas = false, + includedSchemas, + excludedSchemas, + }: { + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + } = {}): Promise> { + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + let allTableM2oAndO2oRelationships: PostgresRelationship[] + { + const sql = TABLE_RELATIONSHIPS_SQL({ schemaFilter }) + const { data, error } = (await this.query(sql)) as PostgresMetaResult + if (error) { + return { data: null, error } + } + allTableM2oAndO2oRelationships = data + } + + /* + * Adapted from: + * https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L392 + */ + let allViewM2oAndO2oRelationships: PostgresRelationship[] + { + type ColDep = { + table_column: string + view_columns: string[] + } + type KeyDep = { + table_schema: string + table_name: string + view_schema: string + view_name: string + constraint_name: string + constraint_type: 'f' | 'f_ref' | 'p' | 'p_ref' + column_dependencies: ColDep[] + } + + const viewsKeyDependenciesSql = VIEWS_KEY_DEPENDENCIES_SQL({ schemaFilter }) + const { data: viewsKeyDependencies, error } = (await this.query( + viewsKeyDependenciesSql + )) as PostgresMetaResult + if (error) { + return { data: null, error } + } + + const viewRelationships = allTableM2oAndO2oRelationships.flatMap((r) => { + const expandKeyDepCols = ( + colDeps: ColDep[] + ): { tableColumns: string[]; viewColumns: string[] }[] => { + const tableColumns = colDeps.map(({ table_column }) => table_column) + // https://gist.github.com/ssippe/1f92625532eef28be6974f898efb23ef?permalink_comment_id=3474581#gistcomment-3474581 + const cartesianProduct = (allEntries: T[][]): T[][] => { + return allEntries.reduce( + (results, entries) => + results + .map((result) => entries.map((entry) => result.concat(entry))) + .reduce((subResults, result) => subResults.concat(result), []), + [[]] + ) + } + const viewColumnsPermutations = cartesianProduct(colDeps.map((cd) => cd.view_columns)) + return viewColumnsPermutations.map((viewColumns) => ({ tableColumns, viewColumns })) + } + + const viewToTableKeyDeps = viewsKeyDependencies.filter( + (vkd) => + vkd.table_schema === r.schema && + vkd.table_name === r.relation && + vkd.constraint_name === r.foreign_key_name && + vkd.constraint_type === 'f' + ) + const tableToViewKeyDeps = viewsKeyDependencies.filter( + (vkd) => + vkd.table_schema === r.referenced_schema && + vkd.table_name === r.referenced_relation && + vkd.constraint_name === r.foreign_key_name && + vkd.constraint_type === 'f_ref' + ) + + const viewToTableRelationships = viewToTableKeyDeps.flatMap((vtkd) => + expandKeyDepCols(vtkd.column_dependencies).map(({ viewColumns }) => ({ + foreign_key_name: r.foreign_key_name, + schema: vtkd.view_schema, + relation: vtkd.view_name, + columns: viewColumns, + is_one_to_one: r.is_one_to_one, + referenced_schema: r.referenced_schema, + referenced_relation: r.referenced_relation, + referenced_columns: r.referenced_columns, + })) + ) + + const tableToViewRelationships = tableToViewKeyDeps.flatMap((tvkd) => + expandKeyDepCols(tvkd.column_dependencies).map(({ viewColumns }) => ({ + foreign_key_name: r.foreign_key_name, + schema: r.schema, + relation: r.relation, + columns: r.columns, + is_one_to_one: r.is_one_to_one, + referenced_schema: tvkd.view_schema, + referenced_relation: tvkd.view_name, + referenced_columns: viewColumns, + })) + ) + + const viewToViewRelationships = viewToTableKeyDeps.flatMap((vtkd) => + expandKeyDepCols(vtkd.column_dependencies).flatMap(({ viewColumns }) => + tableToViewKeyDeps.flatMap((tvkd) => + expandKeyDepCols(tvkd.column_dependencies).map( + ({ viewColumns: referencedViewColumns }) => ({ + foreign_key_name: r.foreign_key_name, + schema: vtkd.view_schema, + relation: vtkd.view_name, + columns: viewColumns, + is_one_to_one: r.is_one_to_one, + referenced_schema: tvkd.view_schema, + referenced_relation: tvkd.view_name, + referenced_columns: referencedViewColumns, + }) + ) + ) + ) + ) + + return [ + ...viewToTableRelationships, + ...tableToViewRelationships, + ...viewToViewRelationships, + ] + }) + + allViewM2oAndO2oRelationships = viewRelationships + } + + return { + data: allTableM2oAndO2oRelationships.concat(allViewM2oAndO2oRelationships), + error: null, + } + } +} diff --git a/src/lib/PostgresMetaRoles.ts b/src/lib/PostgresMetaRoles.ts index 39f3adc3..537b0622 100644 --- a/src/lib/PostgresMetaRoles.ts +++ b/src/lib/PostgresMetaRoles.ts @@ -1,9 +1,22 @@ import { ident, literal } from 'pg-format' -import { DEFAULT_ROLES, DEFAULT_SYSTEM_SCHEMAS } from './constants' -import { coalesceRowsToArray } from './helpers' -import { grantsSql, rolesSql } from './sql' -import { PostgresMetaResult, PostgresRole } from './types' - +import { ROLES_SQL } from './sql/roles.sql.js' +import { + PostgresMetaResult, + PostgresRole, + PostgresRoleCreate, + PostgresRoleUpdate, +} from './types.js' +import { filterByValue } from './helpers.js' +export function changeRoleConfig2Object(config: string[]) { + if (!config) { + return null + } + return config.reduce((acc: any, cur) => { + const [key, value] = cur.split('=') + acc[key] = value + return acc + }, {}) +} export default class PostgresMetaRoles { query: (sql: string) => Promise> @@ -11,26 +24,24 @@ export default class PostgresMetaRoles { this.query = query } - async list({ includeDefaultRoles = false, includeSystemSchemas = false } = {}): Promise< - PostgresMetaResult - > { - const sql = ` -WITH roles AS (${ - includeDefaultRoles - ? rolesSql - : `${rolesSql} WHERE NOT (rolname IN (${DEFAULT_ROLES.map(literal).join(',')}))` - }), - grants AS (${ - includeSystemSchemas - ? grantsSql - : `${grantsSql} AND NOT (nc.nspname IN (${DEFAULT_SYSTEM_SCHEMAS.map(literal).join(',')}))` - }) -SELECT - *, - ${coalesceRowsToArray('grants', 'SELECT * FROM grants WHERE grants.grantee = roles.name')} -FROM - roles;` - return await this.query(sql) + async list({ + includeDefaultRoles = false, + limit, + offset, + }: { + includeDefaultRoles?: boolean + limit?: number + offset?: number + } = {}): Promise> { + const sql = ROLES_SQL({ limit, offset, includeDefaultRoles }) + const result = await this.query(sql) + if (result.data) { + result.data = result.data.map((role: any) => { + role.config = changeRoleConfig2Object(role.config) + return role + }) + } + return result } async retrieve({ id }: { id: number }): Promise> @@ -43,23 +54,28 @@ FROM name?: string }): Promise> { if (id) { - const sql = `${rolesSql} WHERE oid = ${literal(id)};` + const idsFilter = filterByValue([id]) + const sql = ROLES_SQL({ idsFilter }) const { data, error } = await this.query(sql) + if (error) { return { data, error } } else if (data.length === 0) { return { data: null, error: { message: `Cannot find a role with ID ${id}` } } } else { + data[0].config = changeRoleConfig2Object(data[0].config) return { data: data[0], error } } } else if (name) { - const sql = `${rolesSql} WHERE rolname = ${literal(name)};` + const nameFilter = filterByValue([name]) + const sql = ROLES_SQL({ nameFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } } else if (data.length === 0) { return { data: null, error: { message: `Cannot find a role named ${name}` } } } else { + data[0].config = changeRoleConfig2Object(data[0].config) return { data: data[0], error } } } else { @@ -82,22 +98,8 @@ FROM member_of, members, admins, - }: { - name: string - is_superuser?: boolean - can_create_db?: boolean - can_create_role?: boolean - inherit_role?: boolean - can_login?: boolean - is_replication_role?: boolean - can_bypass_rls?: boolean - connection_limit?: number - password?: string - valid_until?: string - member_of?: string[] - members?: string[] - admins?: string[] - }): Promise> { + config, + }: PostgresRoleCreate): Promise> { const isSuperuserClause = is_superuser ? 'SUPERUSER' : 'NOSUPERUSER' const canCreateDbClause = can_create_db ? 'CREATEDB' : 'NOCREATEDB' const canCreateRoleClause = can_create_role ? 'CREATEROLE' : 'NOCREATEROLE' @@ -111,8 +113,20 @@ FROM const memberOfClause = member_of === undefined ? '' : `IN ROLE ${member_of.join(',')}` const membersClause = members === undefined ? '' : `ROLE ${members.join(',')}` const adminsClause = admins === undefined ? '' : `ADMIN ${admins.join(',')}` - + let configClause = '' + if (config !== undefined) { + configClause = Object.keys(config) + .map((k) => { + const v = config[k] + if (!k || !v) { + return '' + } + return `ALTER ROLE ${name} SET ${k} = ${v};` + }) + .join('\n') + } const sql = ` +BEGIN; CREATE ROLE ${ident(name)} WITH ${isSuperuserClause} @@ -127,7 +141,9 @@ WITH ${validUntilClause} ${memberOfClause} ${membersClause} - ${adminsClause};` + ${adminsClause}; +${configClause ? configClause : ''} +COMMIT;` const { error } = await this.query(sql) if (error) { return { data: null, error } @@ -149,19 +165,8 @@ WITH connection_limit, password, valid_until, - }: { - name?: string - is_superuser?: boolean - can_create_db?: boolean - can_create_role?: boolean - inherit_role?: boolean - can_login?: boolean - is_replication_role?: boolean - can_bypass_rls?: boolean - connection_limit?: number - password?: string - valid_until?: string - } + config, + }: PostgresRoleUpdate ): Promise> { const { data: old, error } = await this.retrieve({ id }) if (error) { @@ -202,7 +207,27 @@ WITH connection_limit === undefined ? '' : `CONNECTION LIMIT ${connection_limit}` const passwordClause = password === undefined ? '' : `PASSWORD ${literal(password)}` const validUntilClause = valid_until === undefined ? '' : `VALID UNTIL ${literal(valid_until)}` - + let configClause = '' + if (config !== undefined) { + const configSql = config.map((c) => { + const { op, path, value } = c + const k = path + const v = value || null + if (!k) { + throw new Error(`Invalid config value ${value}`) + } + switch (op) { + case 'add': + case 'replace': + return `ALTER ROLE ${ident(old!.name)} SET ${ident(k)} = ${literal(v)};` + case 'remove': + return `ALTER ROLE ${ident(old!.name)} RESET ${ident(k)};` + default: + throw new Error(`Invalid config op ${op}`) + } + }) + configClause = configSql.filter(Boolean).join('') + } // nameSql must be last const sql = ` BEGIN; @@ -217,6 +242,7 @@ BEGIN; ${connectionLimitClause} ${passwordClause} ${validUntilClause}; + ${configClause ? configClause : ''} ${nameSql} COMMIT;` { diff --git a/src/lib/PostgresMetaSchemas.ts b/src/lib/PostgresMetaSchemas.ts index d44755c6..aa17bcfd 100644 --- a/src/lib/PostgresMetaSchemas.ts +++ b/src/lib/PostgresMetaSchemas.ts @@ -1,12 +1,13 @@ -import { ident, literal } from 'pg-format' -import { DEFAULT_SYSTEM_SCHEMAS } from './constants' -import { schemasSql } from './sql' +import { ident } from 'pg-format' +import { SCHEMAS_SQL } from './sql/schemas.sql.js' import { PostgresMetaResult, PostgresSchema, PostgresSchemaCreate, PostgresSchemaUpdate, -} from './types' +} from './types.js' +import { filterByList, filterByValue } from './helpers.js' +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' export default class PostgresMetaSchemas { query: (sql: string) => Promise> @@ -15,10 +16,25 @@ export default class PostgresMetaSchemas { this.query = query } - async list({ includeSystemSchemas = false } = {}): Promise> { - const sql = includeSystemSchemas - ? schemasSql - : `${schemasSql} AND NOT (n.nspname IN (${DEFAULT_SYSTEM_SCHEMAS.map(literal).join(',')}));` + async list({ + includedSchemas, + excludedSchemas, + includeSystemSchemas = false, + limit, + offset, + }: { + includedSchemas?: string[] + excludedSchemas?: string[] + includeSystemSchemas?: boolean + limit?: number + offset?: number + } = {}): Promise> { + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + const sql = SCHEMAS_SQL({ limit, offset, includeSystemSchemas, nameFilter: schemaFilter }) return await this.query(sql) } @@ -32,7 +48,8 @@ export default class PostgresMetaSchemas { name?: string }): Promise> { if (id) { - const sql = `${schemasSql} AND n.oid = ${literal(id)};` + const idsFilter = filterByValue([id]) + const sql = SCHEMAS_SQL({ idsFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -42,7 +59,8 @@ export default class PostgresMetaSchemas { return { data: data[0], error } } } else if (name) { - const sql = `${schemasSql} AND n.nspname = ${literal(name)};` + const nameFilter = filterByValue([name]) + const sql = SCHEMAS_SQL({ nameFilter }) const { data, error } = await this.query(sql) if (error) { return { data, error } diff --git a/src/lib/PostgresMetaTablePrivileges.ts b/src/lib/PostgresMetaTablePrivileges.ts new file mode 100644 index 00000000..e0e79a05 --- /dev/null +++ b/src/lib/PostgresMetaTablePrivileges.ts @@ -0,0 +1,139 @@ +import { ident } from 'pg-format' +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' +import { filterByList, filterByValue } from './helpers.js' +import { + PostgresMetaResult, + PostgresTablePrivileges, + PostgresTablePrivilegesGrant, + PostgresTablePrivilegesRevoke, +} from './types.js' +import { TABLE_PRIVILEGES_SQL } from './sql/table_privileges.sql.js' + +export default class PostgresMetaTablePrivileges { + query: (sql: string) => Promise> + + constructor(query: (sql: string) => Promise>) { + this.query = query + } + + async list({ + includeSystemSchemas = false, + includedSchemas, + excludedSchemas, + limit, + offset, + }: { + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + } = {}): Promise> { + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + const sql = TABLE_PRIVILEGES_SQL({ schemaFilter, limit, offset }) + return await this.query(sql) + } + + async retrieve({ id }: { id: number }): Promise> + async retrieve({ + name, + schema, + }: { + name: string + schema: string + }): Promise> + async retrieve({ + id, + name, + schema = 'public', + }: { + id?: number + name?: string + schema?: string + }): Promise> { + if (id) { + const idsFilter = filterByValue([id]) + const sql = TABLE_PRIVILEGES_SQL({ idsFilter }) + const { data, error } = await this.query(sql) + if (error) { + return { data, error } + } else if (data.length === 0) { + return { data: null, error: { message: `Cannot find a relation with ID ${id}` } } + } else { + return { data: data[0], error } + } + } else if (name) { + const nameIdentifierFilter = filterByValue([`${schema}.${name}`]) + const sql = TABLE_PRIVILEGES_SQL({ nameIdentifierFilter }) + const { data, error } = await this.query(sql) + if (error) { + return { data, error } + } else if (data.length === 0) { + return { + data: null, + error: { message: `Cannot find a relation named ${name} in schema ${schema}` }, + } + } else { + return { data: data[0], error } + } + } else { + return { data: null, error: { message: 'Invalid parameters on retrieving table privileges' } } + } + } + + async grant( + grants: PostgresTablePrivilegesGrant[] + ): Promise> { + let sql = ` +do $$ +begin +${grants + .map( + ({ privilege_type, relation_id, grantee, is_grantable }) => + `execute format('grant ${privilege_type} on table %s to ${ + grantee.toLowerCase() === 'public' ? 'public' : ident(grantee) + } ${is_grantable ? 'with grant option' : ''}', ${relation_id}::regclass);` + ) + .join('\n')} +end $$; +` + const { data, error } = await this.query(sql) + if (error) { + return { data, error } + } + + // Return the updated table privileges for modified relations. + const relationIds = [...new Set(grants.map(({ relation_id }) => relation_id))] + sql = TABLE_PRIVILEGES_SQL({ idsFilter: filterByList(relationIds) }) + return await this.query(sql) + } + + async revoke( + revokes: PostgresTablePrivilegesRevoke[] + ): Promise> { + let sql = ` +do $$ +begin +${revokes + .map( + (revoke) => + `execute format('revoke ${revoke.privilege_type} on table %s from ${revoke.grantee}', ${revoke.relation_id}::regclass);` + ) + .join('\n')} +end $$; +` + const { data, error } = await this.query(sql) + if (error) { + return { data, error } + } + + // Return the updated table privileges for modified relations. + const relationIds = [...new Set(revokes.map(({ relation_id }) => relation_id))] + sql = TABLE_PRIVILEGES_SQL({ idsFilter: filterByList(relationIds) }) + return await this.query(sql) + } +} diff --git a/src/lib/PostgresMetaTables.ts b/src/lib/PostgresMetaTables.ts index e335dc1f..8d3d9a47 100644 --- a/src/lib/PostgresMetaTables.ts +++ b/src/lib/PostgresMetaTables.ts @@ -1,15 +1,14 @@ import { ident, literal } from 'pg-format' -import { DEFAULT_SYSTEM_SCHEMAS } from './constants' -import { coalesceRowsToArray } from './helpers' +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' +import { coalesceRowsToArray, filterByValue, filterByList } from './helpers.js' import { - columnsSql, - grantsSql, - policiesSql, - primaryKeysSql, - relationshipsSql, - tablesSql, -} from './sql' -import { PostgresMetaResult, PostgresTable } from './types' + PostgresMetaResult, + PostgresTable, + PostgresTableCreate, + PostgresTableUpdate, +} from './types.js' +import { TABLES_SQL } from './sql/table.sql.js' +import { COLUMNS_SQL } from './sql/columns.sql.js' export default class PostgresMetaTables { query: (sql: string) => Promise> @@ -18,12 +17,43 @@ export default class PostgresMetaTables { this.query = query } - async list({ includeSystemSchemas = false } = {}): Promise> { - const sql = includeSystemSchemas - ? enrichedTablesSql - : `${enrichedTablesSql} WHERE NOT (schema IN (${DEFAULT_SYSTEM_SCHEMAS.map(literal).join( - ',' - )}));` + async list(options: { + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + includeColumns: false + }): Promise> + async list(options?: { + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + includeColumns?: boolean + }): Promise> + async list({ + includeSystemSchemas = false, + includedSchemas, + excludedSchemas, + limit, + offset, + includeColumns = true, + }: { + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + includeColumns?: boolean + } = {}): Promise> { + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + const sql = generateEnrichedTablesSql({ includeColumns, schemaFilter, limit, offset }) return await this.query(sql) } @@ -44,8 +74,14 @@ export default class PostgresMetaTables { name?: string schema?: string }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const sql = `${enrichedTablesSql} WHERE tables.id = ${literal(id)};` + const idsFilter = filterByValue([id]) + const sql = generateEnrichedTablesSql({ + schemaFilter, + includeColumns: true, + idsFilter, + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -55,9 +91,12 @@ export default class PostgresMetaTables { return { data: data[0], error } } } else if (name) { - const sql = `${enrichedTablesSql} WHERE tables.name = ${literal( - name - )} AND tables.schema = ${literal(schema)};` + const tableIdentifierFilter = filterByValue([`${schema}.${name}`]) + const sql = generateEnrichedTablesSql({ + schemaFilter, + includeColumns: true, + tableIdentifierFilter, + }) const { data, error } = await this.query(sql) if (error) { return { data, error } @@ -78,11 +117,7 @@ export default class PostgresMetaTables { name, schema = 'public', comment, - }: { - name: string - schema?: string - comment?: string - }): Promise> { + }: PostgresTableCreate): Promise> { const tableSql = `CREATE TABLE ${ident(schema)}.${ident(name)} ();` const commentSql = comment === undefined @@ -105,16 +140,9 @@ export default class PostgresMetaTables { rls_forced, replica_identity, replica_identity_index, + primary_keys, comment, - }: { - name?: string - schema?: string - rls_enabled?: boolean - rls_forced?: boolean - replica_identity?: 'DEFAULT' | 'INDEX' | 'FULL' | 'NOTHING' - replica_identity_index?: string - comment?: string - } + }: PostgresTableUpdate ): Promise> { const { data: old, error } = await this.retrieve({ id }) if (error) { @@ -140,14 +168,42 @@ export default class PostgresMetaTables { const disable = `${alter} NO FORCE ROW LEVEL SECURITY;` forceRls = rls_forced ? enable : disable } - let replicaSql: string + let replicaSql = '' if (replica_identity === undefined) { - replicaSql = '' + // skip } else if (replica_identity === 'INDEX') { replicaSql = `${alter} REPLICA IDENTITY USING INDEX ${replica_identity_index};` } else { replicaSql = `${alter} REPLICA IDENTITY ${replica_identity};` } + let primaryKeysSql = '' + if (primary_keys === undefined) { + // skip + } else { + if (old!.primary_keys.length !== 0) { + primaryKeysSql += ` +DO $$ +DECLARE + r record; +BEGIN + SELECT conname + INTO r + FROM pg_constraint + WHERE contype = 'p' AND conrelid = ${literal(id)}; + EXECUTE ${literal(`${alter} DROP CONSTRAINT `)} || quote_ident(r.conname); +END +$$; +` + } + + if (primary_keys.length === 0) { + // skip + } else { + primaryKeysSql += `${alter} ADD PRIMARY KEY (${primary_keys + .map((x) => ident(x.name)) + .join(',')});` + } + } const commentSql = comment === undefined ? '' @@ -158,6 +214,7 @@ BEGIN; ${enableRls} ${forceRls} ${replicaSql} + ${primaryKeysSql} ${commentSql} ${schemaSql} ${nameSql} @@ -189,30 +246,24 @@ COMMIT;` } } -const enrichedTablesSql = ` -WITH tables AS (${tablesSql}), - columns AS (${columnsSql}), - grants AS (${grantsSql}), - policies AS (${policiesSql}), - primary_keys AS (${primaryKeysSql}), - relationships AS (${relationshipsSql}) -SELECT - *, - ${coalesceRowsToArray('columns', 'SELECT * FROM columns WHERE columns.table_id = tables.id')}, - ${coalesceRowsToArray('grants', 'SELECT * FROM grants WHERE grants.table_id = tables.id')}, - ${coalesceRowsToArray('policies', 'SELECT * FROM policies WHERE policies.table_id = tables.id')}, - ${coalesceRowsToArray( - 'primary_keys', - 'SELECT * FROM primary_keys WHERE primary_keys.table_id = tables.id' - )}, - ${coalesceRowsToArray( - 'relationships', - `SELECT - * - FROM - relationships - WHERE - (relationships.source_schema = tables.schema AND relationships.source_table_name = tables.name) - OR (relationships.target_table_schema = tables.schema AND relationships.target_table_name = tables.name)` - )} -FROM tables` +const generateEnrichedTablesSql = ({ + includeColumns, + schemaFilter, + tableIdentifierFilter, + idsFilter, + limit, + offset, +}: { + includeColumns: boolean + schemaFilter?: string + tableIdentifierFilter?: string + idsFilter?: string + limit?: number + offset?: number +}) => ` +with tables as (${TABLES_SQL({ schemaFilter, tableIdentifierFilter, idsFilter, limit, offset })}) + ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, tableIdFilter: idsFilter, tableIdentifierFilter: tableIdentifierFilter })})` : ''} +select + * + ${includeColumns ? `, ${coalesceRowsToArray('columns', 'columns.table_id = tables.id')}` : ''} +from tables` diff --git a/src/lib/PostgresMetaTriggers.ts b/src/lib/PostgresMetaTriggers.ts index e2baf10d..f7dfbc95 100644 --- a/src/lib/PostgresMetaTriggers.ts +++ b/src/lib/PostgresMetaTriggers.ts @@ -1,6 +1,8 @@ import { ident, literal } from 'pg-format' -import { triggersSql } from './sql' -import { PostgresMetaResult, PostgresTrigger } from './types' +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' +import { filterByList, filterByValue } from './helpers.js' +import { PostgresMetaResult, PostgresTrigger } from './types.js' +import { TRIGGERS_SQL } from './sql/triggers.sql.js' export default class PostgresMetaTriggers { query: (sql: string) => Promise> @@ -9,8 +11,26 @@ export default class PostgresMetaTriggers { this.query = query } - async list(): Promise> { - return await this.query(enrichedTriggersSql) + async list({ + includeSystemSchemas = false, + includedSchemas, + excludedSchemas, + limit, + offset, + }: { + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + } = {}): Promise> { + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + let sql = TRIGGERS_SQL({ schemaFilter, limit, offset }) + return await this.query(sql) } async retrieve({ id }: { id: number }): Promise> @@ -34,8 +54,10 @@ export default class PostgresMetaTriggers { schema?: string table?: string }): Promise> { + const schemaFilter = schema ? filterByList([schema], []) : undefined if (id) { - const sql = `${enrichedTriggersSql} WHERE id = ${literal(id)};` + const idsFilter = filterByValue([id]) + const sql = TRIGGERS_SQL({ idsFilter }) const { data, error } = await this.query(sql) @@ -53,9 +75,9 @@ export default class PostgresMetaTriggers { } if (name && schema && table) { - const sql = `${enrichedTriggersSql} WHERE name = ${literal(name)} AND schema = ${literal( - schema - )} AND triggers.table = ${literal(table)};` + const nameFilter = filterByValue([name]) + const tableNameFilter = filterByValue([table]) + const sql = TRIGGERS_SQL({ schemaFilter, nameFilter, tableNameFilter }) const { data, error } = await this.query(sql) @@ -130,14 +152,15 @@ export default class PostgresMetaTriggers { const triggerCondition = condition ? `WHEN (${condition})` : '' const functionArgs = `${function_args?.map(literal).join(',') ?? ''}` - const sql = `CREATE TRIGGER ${ident(name)} ${activation} ${triggerEvents} ON ${qualifiedTableName} ${triggerOrientation} ${triggerCondition} EXECUTE FUNCTION ${qualifiedFunctionName}(${functionArgs});` + const sql = `CREATE TRIGGER ${ident( + name + )} ${activation} ${triggerEvents} ON ${qualifiedTableName} ${triggerOrientation} ${triggerCondition} EXECUTE FUNCTION ${qualifiedFunctionName}(${functionArgs});` const { error } = await this.query(sql) if (error) { return { data: null, error } } - return await this.retrieve({ name, table, @@ -148,40 +171,48 @@ export default class PostgresMetaTriggers { async update( id: number, { - name: newName, + name, enabled_mode, }: { - name: string - enabled_mode: 'ORIGIN' | 'REPLICA' | 'ALWAYS' | 'DISABLED' + name?: string + enabled_mode?: 'ORIGIN' | 'REPLICA' | 'ALWAYS' | 'DISABLED' } ): Promise> { - const { data: triggerRecord, error } = await this.retrieve({ id }) - + const { data: old, error } = await this.retrieve({ id }) if (error) { return { data: null, error } } - let enabledModeSql - const enabledMode = enabled_mode.toUpperCase() - const { name: currentName, schema: schema, table: table } = triggerRecord! - const qualifiedTableName = `${ident(schema)}.${ident(table)}` - const updateNameSql = newName - ? `ALTER TRIGGER ${ident(currentName)} ON ${qualifiedTableName} RENAME TO ${ident(newName)};` - : '' - - if (['ORIGIN', 'REPLICA', 'ALWAYS', 'DISABLED'].includes(enabledMode)) { - if (enabledMode === 'DISABLED') { - enabledModeSql = `ALTER TABLE ${qualifiedTableName} DISABLE TRIGGER ${ident(currentName)};` - } else { - enabledModeSql = `ALTER TABLE ${qualifiedTableName} ENABLE ${ - ['REPLICA', 'ALWAYS'].includes(enabledMode) ? enabledMode : '' - } TRIGGER ${ident(currentName)};` - } + let enabledModeSql = '' + switch (enabled_mode) { + case 'ORIGIN': + enabledModeSql = `ALTER TABLE ${ident(old!.schema)}.${ident( + old!.table + )} ENABLE TRIGGER ${ident(old!.name)};` + break + case 'DISABLED': + enabledModeSql = `ALTER TABLE ${ident(old!.schema)}.${ident( + old!.table + )} DISABLE TRIGGER ${ident(old!.name)};` + break + case 'REPLICA': + case 'ALWAYS': + enabledModeSql = `ALTER TABLE ${ident(old!.schema)}.${ident( + old!.table + )} ENABLE ${enabled_mode} TRIGGER ${ident(old!.name)};` + break + default: + break } + const nameSql = + name && name !== old!.name + ? `ALTER TRIGGER ${ident(old!.name)} ON ${ident(old!.schema)}.${ident( + old!.table + )} RENAME TO ${ident(name)};` + : '' // updateNameSql must be last - const sql = `BEGIN; ${enabledModeSql} ${updateNameSql} COMMIT;` - + const sql = `BEGIN; ${enabledModeSql}; ${nameSql}; COMMIT;` { const { error } = await this.query(sql) @@ -189,11 +220,10 @@ export default class PostgresMetaTriggers { return { data: null, error } } } - return await this.retrieve({ id }) } - async remove(id: number, { cascade = false }): Promise> { + async remove(id: number, { cascade = false } = {}): Promise> { const { data: triggerRecord, error } = await this.retrieve({ id }) if (error) { @@ -216,12 +246,3 @@ export default class PostgresMetaTriggers { return { data: triggerRecord!, error: null } } } - -const enrichedTriggersSql = ` - WITH triggers AS ( - ${triggersSql} - ) - SELECT - * - FROM triggers -` diff --git a/src/lib/PostgresMetaTypes.ts b/src/lib/PostgresMetaTypes.ts index 740be365..990c94e3 100644 --- a/src/lib/PostgresMetaTypes.ts +++ b/src/lib/PostgresMetaTypes.ts @@ -1,7 +1,7 @@ -import { literal } from 'pg-format' -import { DEFAULT_SYSTEM_SCHEMAS } from './constants' -import { typesSql } from './sql' -import { PostgresMetaResult, PostgresType } from './types' +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' +import { filterByList } from './helpers.js' +import { PostgresMetaResult, PostgresType } from './types.js' +import { TYPES_SQL } from './sql/types.sql.js' export default class PostgresMetaTypes { query: (sql: string) => Promise> @@ -10,10 +10,29 @@ export default class PostgresMetaTypes { this.query = query } - async list({ includeSystemSchemas = false } = {}): Promise> { - const sql = includeSystemSchemas - ? typesSql - : `${typesSql} AND NOT (n.nspname IN (${DEFAULT_SYSTEM_SCHEMAS.map(literal).join(',')}));` + async list({ + includeTableTypes = false, + includeArrayTypes = false, + includeSystemSchemas = false, + includedSchemas, + excludedSchemas, + limit, + offset, + }: { + includeTableTypes?: boolean + includeArrayTypes?: boolean + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + } = {}): Promise> { + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + const sql = TYPES_SQL({ schemaFilter, limit, offset, includeTableTypes, includeArrayTypes }) return await this.query(sql) } } diff --git a/src/lib/PostgresMetaVersion.ts b/src/lib/PostgresMetaVersion.ts index 184991f0..5ea23f37 100644 --- a/src/lib/PostgresMetaVersion.ts +++ b/src/lib/PostgresMetaVersion.ts @@ -1,5 +1,5 @@ -import { versionSql } from './sql' -import { PostgresMetaResult, PostgresVersion } from './types' +import { VERSION_SQL } from './sql/version.sql.js' +import { PostgresMetaResult, PostgresVersion } from './types.js' export default class PostgresMetaVersion { query: (sql: string) => Promise> @@ -9,7 +9,7 @@ export default class PostgresMetaVersion { } async retrieve(): Promise> { - const { data, error } = await this.query(versionSql) + const { data, error } = await this.query(VERSION_SQL()) if (error) { return { data, error } } diff --git a/src/lib/PostgresMetaViews.ts b/src/lib/PostgresMetaViews.ts new file mode 100644 index 00000000..a9e7b0ce --- /dev/null +++ b/src/lib/PostgresMetaViews.ts @@ -0,0 +1,112 @@ +import { DEFAULT_SYSTEM_SCHEMAS } from './constants.js' +import { coalesceRowsToArray, filterByList, filterByValue } from './helpers.js' +import { PostgresMetaResult, PostgresView } from './types.js' +import { VIEWS_SQL } from './sql/views.sql.js' +import { COLUMNS_SQL } from './sql/columns.sql.js' + +export default class PostgresMetaViews { + query: (sql: string) => Promise> + + constructor(query: (sql: string) => Promise>) { + this.query = query + } + + async list({ + includeSystemSchemas = false, + includedSchemas, + excludedSchemas, + limit, + offset, + includeColumns = true, + }: { + includeSystemSchemas?: boolean + includedSchemas?: string[] + excludedSchemas?: string[] + limit?: number + offset?: number + includeColumns?: boolean + } = {}): Promise> { + const schemaFilter = filterByList( + includedSchemas, + excludedSchemas, + !includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined + ) + const sql = generateEnrichedViewsSql({ includeColumns, schemaFilter, limit, offset }) + return await this.query(sql) + } + + async retrieve({ id }: { id: number }): Promise> + async retrieve({ + name, + schema, + }: { + name: string + schema: string + }): Promise> + async retrieve({ + id, + name, + schema = 'public', + }: { + id?: number + name?: string + schema?: string + }): Promise> { + if (id) { + const idsFilter = filterByValue([id]) + const sql = generateEnrichedViewsSql({ + includeColumns: true, + idsFilter, + }) + const { data, error } = await this.query(sql) + if (error) { + return { data, error } + } else if (data.length === 0) { + return { data: null, error: { message: `Cannot find a view with ID ${id}` } } + } else { + return { data: data[0], error } + } + } else if (name) { + const viewIdentifierFilter = filterByValue([`${schema}.${name}`]) + const sql = generateEnrichedViewsSql({ + includeColumns: true, + viewIdentifierFilter, + }) + const { data, error } = await this.query(sql) + if (error) { + return { data, error } + } else if (data.length === 0) { + return { + data: null, + error: { message: `Cannot find a view named ${name} in schema ${schema}` }, + } + } else { + return { data: data[0], error } + } + } else { + return { data: null, error: { message: 'Invalid parameters on view retrieve' } } + } + } +} + +const generateEnrichedViewsSql = ({ + includeColumns, + schemaFilter, + idsFilter, + viewIdentifierFilter, + limit, + offset, +}: { + includeColumns: boolean + schemaFilter?: string + idsFilter?: string + viewIdentifierFilter?: string + limit?: number + offset?: number +}) => ` +with views as (${VIEWS_SQL({ schemaFilter, limit, offset, viewIdentifierFilter, idsFilter })}) + ${includeColumns ? `, columns as (${COLUMNS_SQL({ schemaFilter, tableIdentifierFilter: viewIdentifierFilter, tableIdFilter: idsFilter })})` : ''} +select + * + ${includeColumns ? `, ${coalesceRowsToArray('columns', 'columns.table_id = views.id')}` : ''} +from views` diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 8b51c721..c28ea770 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,18 +1 @@ -export const DEFAULT_ROLES = [ - 'pg_execute_server_program', - 'pg_monitor', - 'pg_read_all_settings', - 'pg_read_all_stats', - 'pg_read_server_files', - 'pg_signal_backend', - 'pg_stat_scan_tables', - 'pg_write_server_files', -] - -export const DEFAULT_SYSTEM_SCHEMAS = [ - 'information_schema', - 'pg_catalog', - 'pg_temp_1', - 'pg_toast', - 'pg_toast_temp_1', -] +export const DEFAULT_SYSTEM_SCHEMAS = ['information_schema', 'pg_catalog', 'pg_toast'] diff --git a/src/lib/db.ts b/src/lib/db.ts index e7957110..d43ef8f5 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -1,44 +1,266 @@ -import { types, Pool, PoolConfig } from 'pg' -import { PostgresMetaResult } from './types' +import pg from 'pg' +import * as Sentry from '@sentry/node' +import { parse as parseArray } from 'postgres-array' +import { PostgresMetaResult, PoolConfig } from './types.js' -types.setTypeParser(20, parseInt) +pg.types.setTypeParser(pg.types.builtins.INT8, (x) => { + const asNumber = Number(x) + if (Number.isSafeInteger(asNumber)) { + return asNumber + } else { + return x + } +}) +pg.types.setTypeParser(pg.types.builtins.DATE, (x) => x) +pg.types.setTypeParser(pg.types.builtins.INTERVAL, (x) => x) +pg.types.setTypeParser(pg.types.builtins.TIMESTAMP, (x) => x) +pg.types.setTypeParser(pg.types.builtins.TIMESTAMPTZ, (x) => x) +pg.types.setTypeParser(1115, parseArray) // _timestamp +pg.types.setTypeParser(1182, parseArray) // _date +pg.types.setTypeParser(1185, parseArray) // _timestamptz +pg.types.setTypeParser(600, (x) => x) // point +pg.types.setTypeParser(1017, (x) => x) // _point + +// Ensure any query will have an appropriate error handler on the pool to prevent connections errors +// to bubble up all the stack eventually killing the server +const poolerQueryHandleError = ( + pgpool: pg.Pool, + sql: string, + parameters?: unknown[] +): Promise> => { + return Sentry.startSpan( + { op: 'db', name: 'poolerQuery' }, + () => + new Promise((resolve, reject) => { + let rejected = false + const connectionErrorHandler = (err: any) => { + // If the error hasn't already be propagated to the catch + if (!rejected) { + // This is a trick to wait for the next tick, leaving a chance for handled errors such as + // RESULT_SIZE_LIMIT to take over other stream errors such as `unexpected commandComplete message` + setTimeout(() => { + rejected = true + return reject(err) + }) + } + } + // This listened avoid getting uncaught exceptions for errors happening at connection level within the stream + // such as parse or RESULT_SIZE_EXCEEDED errors instead, handle the error gracefully by bubbling in up to the caller + pgpool.once('error', connectionErrorHandler) + pgpool + .query(sql, parameters) + .then((results: pg.QueryResult) => { + if (!rejected) { + return resolve(results) + } + }) + .catch((err: any) => { + // If the error hasn't already be handled within the error listener + if (!rejected) { + rejected = true + return reject(err) + } + }) + }) + ) +} -export const init: ( - config: PoolConfig -) => { - query: (sql: string) => Promise> +export const init: (config: PoolConfig) => { + query: ( + sql: string, + opts?: { statementQueryTimeout?: number; trackQueryInSentry?: boolean; parameters?: unknown[] } + ) => Promise> end: () => Promise } = (config) => { - // XXX: Race condition could happen here: one async task may be doing - // `pool.end()` which invalidates the pool and subsequently all existing - // handles to `query`. Normally you might only deal with one DB so you don't - // need to call `pool.end()`, but since the server needs this, we make a - // compromise: if we run `query` after `pool.end()` is called (i.e. pool is - // `null`), we temporarily create a pool and close is right after. - let pool: Pool | null = new Pool(config) - return { - async query(sql) { - try { - if (!pool) { - const pool = new Pool(config) - const { rows } = await pool.query(sql) - await pool.end() - return { data: rows, error: null } - } + return Sentry.startSpan({ op: 'db', name: 'db.init' }, () => { + // node-postgres ignores config.ssl if any of sslmode, sslca, sslkey, sslcert, + // sslrootcert are in the connection string. Here we allow setting sslmode in + // the connection string while setting the rest in config.ssl. + if (config.connectionString) { + const u = new URL(config.connectionString) + const sslmode = u.searchParams.get('sslmode') + u.searchParams.delete('sslmode') + // For now, we don't support setting these from the connection string. + u.searchParams.delete('sslca') + u.searchParams.delete('sslkey') + u.searchParams.delete('sslcert') + u.searchParams.delete('sslrootcert') + config.connectionString = u.toString() - const { rows } = await pool.query(sql) - return { data: rows, error: null } - } catch (e) { - return { data: null, error: { message: e.message } } + // sslmode: null, 'disable', 'prefer', 'require', 'verify-ca', 'verify-full', 'no-verify' + // config.ssl: true, false, {} + if (sslmode === null) { + // skip + } else if (sslmode === 'disable') { + config.ssl = false + } else { + if (typeof config.ssl !== 'object') { + config.ssl = {} + } + config.ssl.rejectUnauthorized = sslmode === 'verify-full' } - }, - - async end() { - const _pool = pool - pool = null - // Gracefully wait for active connections to be idle, then close all - // connections in the pool. - if (_pool) await _pool.end() - }, - } + } + + // NOTE: Race condition could happen here: one async task may be doing + // `pool.end()` which invalidates the pool and subsequently all existing + // handles to `query`. Normally you might only deal with one DB so you don't + // need to call `pool.end()`, but since the server needs this, we make a + // compromise: if we run `query` after `pool.end()` is called (i.e. pool is + // `null`), we temporarily create a pool and close it right after. + let pool: pg.Pool | null = new pg.Pool(config) + + return { + async query( + sql, + { statementQueryTimeout, trackQueryInSentry, parameters } = { trackQueryInSentry: true } + ) { + return Sentry.startSpan( + // For metrics purposes, log the query that will be run if it's not an user provided query (with possibly sentitives infos) + { + op: 'db', + name: 'init.query', + attributes: { sql: trackQueryInSentry ? sql : 'custom' }, + }, + async () => { + // Use statement_timeout AND idle_session_timeout to ensure the connection will be killed even if idle after + // timeout time. + const statementTimeoutQueryPrefix = statementQueryTimeout + ? `SET statement_timeout='${statementQueryTimeout}s'; SET idle_session_timeout='${statementQueryTimeout}s';` + : '' + // node-postgres need a statement_timeout to kill the connection when timeout is reached + // otherwise the query will keep running on the database even if query timeout was reached + // This need to be added at query and not connection level because poolers (pgbouncer) doesn't + // allow to set this parameter at connection time + const sqlWithStatementTimeout = `${statementTimeoutQueryPrefix}${sql}` + try { + if (!pool) { + const pool = new pg.Pool(config) + let res = await poolerQueryHandleError(pool, sqlWithStatementTimeout, parameters) + if (Array.isArray(res)) { + res = res.reverse().find((x) => x.rows.length !== 0) ?? { rows: [] } + } + await pool.end() + return { data: res.rows, error: null } + } + + let res = await poolerQueryHandleError(pool, sqlWithStatementTimeout, parameters) + if (Array.isArray(res)) { + res = res.reverse().find((x) => x.rows.length !== 0) ?? { rows: [] } + } + return { data: res.rows, error: null } + } catch (error: any) { + if (error.constructor.name === 'DatabaseError') { + // Roughly based on: + // - https://github.com/postgres/postgres/blob/fc4089f3c65a5f1b413a3299ba02b66a8e5e37d0/src/interfaces/libpq/fe-protocol3.c#L1018 + // - https://github.com/brianc/node-postgres/blob/b1a8947738ce0af004cb926f79829bb2abc64aa6/packages/pg/lib/native/query.js#L33 + let formattedError = '' + { + if (error.severity) { + formattedError += `${error.severity}: ` + } + if (error.code) { + formattedError += `${error.code}: ` + } + if (error.message) { + formattedError += error.message + } + formattedError += '\n' + if (error.position) { + // error.position is 1-based + // we also remove our `SET statement_timeout = 'XXs';\n` from the position + const position = Number(error.position) - 1 - statementTimeoutQueryPrefix.length + // we set the new error position + error.position = `${position + 1}` + + let line = '' + let lineNumber = 0 + let lineOffset = 0 + + const lines = sql.split('\n') + let currentOffset = 0 + for (let i = 0; i < lines.length; i++) { + if (currentOffset + lines[i].length > position) { + line = lines[i] + lineNumber = i + 1 // 1-based + lineOffset = position - currentOffset + break + } + currentOffset += lines[i].length + 1 // 1 extra offset for newline + } + formattedError += `LINE ${lineNumber}: ${line}\n${' '.repeat(5 + lineNumber.toString().length + 2 + lineOffset)}^\n` + } + if (error.detail) { + formattedError += `DETAIL: ${error.detail}\n` + } + if (error.hint) { + formattedError += `HINT: ${error.hint}\n` + } + if (error.internalQuery) { + formattedError += `QUERY: ${error.internalQuery}\n` + } + if (error.where) { + formattedError += `CONTEXT: ${error.where}\n` + } + } + + return { + data: null, + error: { + ...error, + // error.message is non-enumerable + message: error.message, + formattedError, + }, + } + } + try { + // Handle stream errors and result size exceeded errors + if (error.code === 'RESULT_SIZE_EXCEEDED') { + // Force kill the connection without waiting for graceful shutdown + return { + data: null, + error: { + message: `Query result size (${error.resultSize} bytes) exceeded the configured limit (${error.maxResultSize} bytes)`, + code: error.code, + resultSize: error.resultSize, + maxResultSize: error.maxResultSize, + }, + } + } + return { data: null, error: { code: error.code, message: error.message } } + } finally { + try { + // If the error isn't a "DatabaseError" assume it's a connection related we kill the connection + // To attempt a clean reconnect on next try + await this.end.bind(this) + } catch (error) { + console.error('Failed to end the connection on error: ', { + this: this, + end: this.end, + }) + } + } + } + } + ) + }, + + async end() { + Sentry.startSpan({ op: 'db', name: 'init.end' }, async () => { + try { + const _pool = pool + pool = null + // Gracefully wait for active connections to be idle, then close all + // connections in the pool. + if (_pool) { + await _pool.end() + } + } catch (endError) { + // Ignore any errors during cleanup just log them + console.error('Failed ending connection pool', endError) + } + }) + }, + } + }) } diff --git a/src/lib/generators.ts b/src/lib/generators.ts new file mode 100644 index 00000000..6b5f55e5 --- /dev/null +++ b/src/lib/generators.ts @@ -0,0 +1,141 @@ +import PostgresMeta from './PostgresMeta.js' +import { + PostgresColumn, + PostgresForeignTable, + PostgresFunction, + PostgresMaterializedView, + PostgresMetaResult, + PostgresRelationship, + PostgresSchema, + PostgresTable, + PostgresType, + PostgresView, +} from './types.js' + +export type GeneratorMetadata = { + schemas: PostgresSchema[] + tables: Omit[] + foreignTables: Omit[] + views: Omit[] + materializedViews: Omit[] + columns: PostgresColumn[] + relationships: PostgresRelationship[] + functions: PostgresFunction[] + types: PostgresType[] +} + +export async function getGeneratorMetadata( + pgMeta: PostgresMeta, + filters: { includedSchemas?: string[]; excludedSchemas?: string[] } = { + includedSchemas: [], + excludedSchemas: [], + } +): Promise> { + const includedSchemas = filters.includedSchemas ?? [] + const excludedSchemas = filters.excludedSchemas ?? [] + + const { data: schemas, error: schemasError } = await pgMeta.schemas.list({ + includeSystemSchemas: false, + includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, + }) + if (schemasError) { + return { data: null, error: schemasError } + } + + const { data: tables, error: tablesError } = await pgMeta.tables.list({ + includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, + includeColumns: false, + }) + if (tablesError) { + return { data: null, error: tablesError } + } + + const { data: foreignTables, error: foreignTablesError } = await pgMeta.foreignTables.list({ + includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, + includeColumns: false, + }) + if (foreignTablesError) { + return { data: null, error: foreignTablesError } + } + + const { data: views, error: viewsError } = await pgMeta.views.list({ + includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, + includeColumns: false, + }) + if (viewsError) { + return { data: null, error: viewsError } + } + + const { data: materializedViews, error: materializedViewsError } = + await pgMeta.materializedViews.list({ + includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, + includeColumns: false, + }) + if (materializedViewsError) { + return { data: null, error: materializedViewsError } + } + + const { data: columns, error: columnsError } = await pgMeta.columns.list({ + includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, + includeSystemSchemas: false, + }) + if (columnsError) { + return { data: null, error: columnsError } + } + + const { data: relationships, error: relationshipsError } = await pgMeta.relationships.list({ + includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, + includeSystemSchemas: false, + }) + if (relationshipsError) { + return { data: null, error: relationshipsError } + } + + const { data: functions, error: functionsError } = await pgMeta.functions.list({ + includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined, + excludedSchemas: excludedSchemas.length > 0 ? excludedSchemas : undefined, + includeSystemSchemas: false, + }) + if (functionsError) { + return { data: null, error: functionsError } + } + + const { data: types, error: typesError } = await pgMeta.types.list({ + includeTableTypes: true, + includeArrayTypes: true, + includeSystemSchemas: true, + }) + if (typesError) { + return { data: null, error: typesError } + } + + await pgMeta.end() + + return { + data: { + schemas: schemas.filter( + ({ name }) => + !excludedSchemas.includes(name) && + (includedSchemas.length === 0 || includedSchemas.includes(name)) + ), + tables, + foreignTables, + views, + materializedViews, + columns, + relationships, + functions: functions.filter( + ({ return_type }) => !['trigger', 'event_trigger'].includes(return_type) + ), + types, + }, + error: null, + } +} diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index 12ac4cfc..4fca3124 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -1,26 +1,37 @@ -export const coalesceRowsToArray = (source: string, joinQuery: string) => { - // Note that array_to_json(array_agg(row_to_json())) seems to perform better than json_agg +import { literal } from 'pg-format' + +export const coalesceRowsToArray = (source: string, filter: string) => { return ` COALESCE( ( SELECT - array_to_json(array_agg(row_to_json(${source}))) + array_agg(row_to_json(${source})) FILTER (WHERE ${filter}) FROM - ( ${joinQuery} ) ${source} + ${source} ), - '[]' + '{}' ) AS ${source}` } -/** - * Transforms an array of SQL strings into a transaction - */ -export const toTransaction = (statements: string[]) => { - let cleansed = statements.map((x) => { - let sql = x.trim() - if (sql.length > 0 && x.slice(-1) !== ';') sql += ';' - return sql - }) - const allStatements = cleansed.join('') - return `BEGIN; ${allStatements} COMMIT;` +export const filterByList = ( + include?: (string | number)[], + exclude?: (string | number)[], + defaultExclude?: (string | number)[] +) => { + if (defaultExclude) { + exclude = defaultExclude.concat(exclude ?? []) + } + if (include?.length) { + return `IN (${include.map(literal).join(',')})` + } else if (exclude?.length) { + return `NOT IN (${exclude.map(literal).join(',')})` + } + return '' +} + +export const filterByValue = (ids?: (string | number)[]) => { + if (ids?.length) { + return `IN (${ids.map(literal).join(',')})` + } + return '' } diff --git a/src/lib/index.ts b/src/lib/index.ts index aa45666b..246b1e71 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,2 +1,26 @@ -import PostgresMeta from './PostgresMeta' -export { PostgresMeta } +export { default as PostgresMeta } from './PostgresMeta.js' +export { + PostgresMetaOk, + PostgresMetaErr, + PostgresMetaResult, + PostgresColumn, + PostgresConfig, + PostgresExtension, + PostgresFunction, + PostgresFunctionCreate, + PostgresIndex, + PostgresMaterializedView, + PostgresPolicy, + PostgresPrimaryKey, + PostgresPublication, + PostgresRelationship, + PostgresRole, + PostgresSchema, + PostgresSchemaCreate, + PostgresSchemaUpdate, + PostgresTable, + PostgresTrigger, + PostgresType, + PostgresVersion, + PostgresView, +} from './types.js' diff --git a/src/lib/secrets.ts b/src/lib/secrets.ts new file mode 100644 index 00000000..c44578ec --- /dev/null +++ b/src/lib/secrets.ts @@ -0,0 +1,24 @@ +export const getSecret = async (key: string) => { + if (!key) { + return '' + } + + const env = process.env[key] + if (env) { + return env + } + + const file = process.env[key + '_FILE'] + if (!file) { + return '' + } + // Use dynamic import to support module mock + const fs = await import('node:fs/promises') + + return await fs.readFile(file, { encoding: 'utf8' }).catch((e) => { + if (e.code == 'ENOENT') { + return '' + } + throw e + }) +} diff --git a/src/lib/sql/column_privileges.sql.ts b/src/lib/sql/column_privileges.sql.ts new file mode 100644 index 00000000..f60101dc --- /dev/null +++ b/src/lib/sql/column_privileges.sql.ts @@ -0,0 +1,157 @@ +import type { SQLQueryPropsWithSchemaFilter } from './common.js' + +export const COLUMN_PRIVILEGES_SQL = ( + props: SQLQueryPropsWithSchemaFilter & { + columnIdsFilter?: string + } +) => /* SQL */ ` +-- Lists each column's privileges in the form of: +-- +-- [ +-- { +-- "column_id": "12345.1", +-- "relation_schema": "public", +-- "relation_name": "mytable", +-- "column_name": "mycolumn", +-- "privileges": [ +-- { +-- "grantor": "postgres", +-- "grantee": "myrole", +-- "privilege_type": "SELECT", +-- "is_grantable": false +-- }, +-- ... +-- ] +-- }, +-- ... +-- ] +-- +-- Modified from information_schema.column_privileges. We try to be as close as +-- possible to the view definition, obtained from: +-- +-- select pg_get_viewdef('information_schema.column_privileges'); +-- +-- The main differences are: +-- - we include column privileges for materialized views +-- (reason for exclusion in information_schema.column_privileges: +-- https://www.postgresql.org/message-id/9136.1502740844%40sss.pgh.pa.us) +-- - we query a.attrelid and a.attnum to generate \`column_id\` +-- - \`table_catalog\` is omitted +-- - table_schema -> relation_schema, table_name -> relation_name +-- +-- Column privileges are intertwined with table privileges in that table +-- privileges override column privileges. E.g. if we do: +-- +-- grant all on mytable to myrole; +-- +-- Then \`myrole\` is granted privileges for ALL columns. Likewise, if we do: +-- +-- grant all (id) on mytable to myrole; +-- revoke all on mytable from myrole; +-- +-- Then the grant on the \`id\` column is revoked. +-- +-- This is unlike how grants for schemas and tables interact, where you need +-- privileges for BOTH the schema the table is in AND the table itself in order +-- to access the table. + +select (x.attrelid || '.' || x.attnum) as column_id, + nc.nspname as relation_schema, + x.relname as relation_name, + x.attname as column_name, + coalesce( + jsonb_agg( + jsonb_build_object( + 'grantor', u_grantor.rolname, + 'grantee', grantee.rolname, + 'privilege_type', x.prtype, + 'is_grantable', x.grantable + ) + ), + '[]' + ) as privileges +from + (select pr_c.grantor, + pr_c.grantee, + a.attrelid, + a.attnum, + a.attname, + pr_c.relname, + pr_c.relnamespace, + pr_c.prtype, + pr_c.grantable, + pr_c.relowner + from + (select pg_class.oid, + pg_class.relname, + pg_class.relnamespace, + pg_class.relowner, + (aclexplode(coalesce(pg_class.relacl, acldefault('r', pg_class.relowner)))).grantor as grantor, + (aclexplode(coalesce(pg_class.relacl, acldefault('r', pg_class.relowner)))).grantee as grantee, + (aclexplode(coalesce(pg_class.relacl, acldefault('r', pg_class.relowner)))).privilege_type as privilege_type, + (aclexplode(coalesce(pg_class.relacl, acldefault('r', pg_class.relowner)))).is_grantable as is_grantable + from pg_class + where (pg_class.relkind = any (array['r', + 'v', + 'm', + 'f', + 'p'])) ) pr_c(oid, relname, relnamespace, relowner, grantor, grantee, prtype, grantable), + pg_attribute a + where ((a.attrelid = pr_c.oid) + and (a.attnum > 0) + and (not a.attisdropped)) + union select pr_a.grantor, + pr_a.grantee, + pr_a.attrelid, + pr_a.attnum, + pr_a.attname, + c.relname, + c.relnamespace, + pr_a.prtype, + pr_a.grantable, + c.relowner + from + (select a.attrelid, + a.attnum, + a.attname, + (aclexplode(coalesce(a.attacl, acldefault('c', cc.relowner)))).grantor as grantor, + (aclexplode(coalesce(a.attacl, acldefault('c', cc.relowner)))).grantee as grantee, + (aclexplode(coalesce(a.attacl, acldefault('c', cc.relowner)))).privilege_type as privilege_type, + (aclexplode(coalesce(a.attacl, acldefault('c', cc.relowner)))).is_grantable as is_grantable + from (pg_attribute a + join pg_class cc on ((a.attrelid = cc.oid))) + where ((a.attnum > 0) + and (not a.attisdropped))) pr_a(attrelid, attnum, attname, grantor, grantee, prtype, grantable), + pg_class c + where ((pr_a.attrelid = c.oid) + and (c.relkind = any (ARRAY['r', + 'v', + 'm', + 'f', + 'p'])))) x, + pg_namespace nc, + pg_authid u_grantor, + (select pg_authid.oid, + pg_authid.rolname + from pg_authid + union all select (0)::oid as oid, + 'PUBLIC') grantee(oid, rolname) +where ((x.relnamespace = nc.oid) + ${props.schemaFilter ? `and nc.nspname ${props.schemaFilter}` : ''} + ${props.columnIdsFilter ? `and (x.attrelid || '.' || x.attnum) ${props.columnIdsFilter}` : ''} + and (x.grantee = grantee.oid) + and (x.grantor = u_grantor.oid) + and (x.prtype = any (ARRAY['INSERT', + 'SELECT', + 'UPDATE', + 'REFERENCES'])) + and (pg_has_role(u_grantor.oid, 'USAGE') + or pg_has_role(grantee.oid, 'USAGE') + or (grantee.rolname = 'PUBLIC'))) +group by column_id, + nc.nspname, + x.relname, + x.attname +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/columns.sql b/src/lib/sql/columns.sql deleted file mode 100644 index 371d7e99..00000000 --- a/src/lib/sql/columns.sql +++ /dev/null @@ -1,97 +0,0 @@ --- Adapted from information_schema.columns - -SELECT - c.oid :: int8 AS table_id, - nc.nspname AS schema, - c.relname AS table, - (c.oid || '.' || a.attnum) AS id, - a.attnum AS ordinal_position, - a.attname AS name, - CASE - WHEN a.atthasdef THEN pg_get_expr(ad.adbin, ad.adrelid) - ELSE NULL - END AS default_value, - CASE - WHEN t.typtype = 'd' THEN CASE - WHEN bt.typelem <> 0 :: oid - AND bt.typlen = -1 THEN 'ARRAY' - WHEN nbt.nspname = 'pg_catalog' THEN format_type(t.typbasetype, NULL) - ELSE 'USER-DEFINED' - END - ELSE CASE - WHEN t.typelem <> 0 :: oid - AND t.typlen = -1 THEN 'ARRAY' - WHEN nt.nspname = 'pg_catalog' THEN format_type(a.atttypid, NULL) - ELSE 'USER-DEFINED' - END - END AS data_type, - COALESCE(bt.typname, t.typname) AS format, - CASE - WHEN a.attidentity IN ('a', 'd') THEN TRUE - ELSE FALSE - END AS is_identity, - CASE - a.attidentity - WHEN 'a' THEN 'ALWAYS' - WHEN 'd' THEN 'BY DEFAULT' - ELSE NULL - END AS identity_generation, - CASE - WHEN a.attnotnull - OR t.typtype = 'd' - AND t.typnotnull THEN FALSE - ELSE TRUE - END AS is_nullable, - CASE - WHEN ( - c.relkind IN ('r', 'p') - ) - OR ( - c.relkind IN ('v', 'f') - ) - AND pg_column_is_updatable(c.oid, a.attnum, FALSE) THEN TRUE - ELSE FALSE - END AS is_updatable, - array_to_json( - array( - SELECT - enumlabel - FROM - pg_catalog.pg_enum enums - WHERE - quote_ident(COALESCE(bt.typname, t.typname)) = format_type(enums.enumtypid, NULL) - ORDER BY - enums.enumsortorder - ) - ) AS enums, - col_description(c.oid, a.attnum) AS comment -FROM - pg_attribute a - LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid - AND a.attnum = ad.adnum - JOIN ( - pg_class c - JOIN pg_namespace nc ON c.relnamespace = nc.oid - ) ON a.attrelid = c.oid - JOIN ( - pg_type t - JOIN pg_namespace nt ON t.typnamespace = nt.oid - ) ON a.atttypid = t.oid - LEFT JOIN ( - pg_type bt - JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid - ) ON t.typtype = 'd' - AND t.typbasetype = bt.oid -WHERE - NOT pg_is_other_temp_schema(nc.oid) - AND a.attnum > 0 - AND NOT a.attisdropped - AND (c.relkind IN ('r', 'v', 'f', 'p')) - AND ( - pg_has_role(c.relowner, 'USAGE') - OR has_column_privilege( - c.oid, - a.attnum, - 'SELECT, INSERT, UPDATE, REFERENCES' - ) - ) diff --git a/src/lib/sql/columns.sql.ts b/src/lib/sql/columns.sql.ts new file mode 100644 index 00000000..d19c968c --- /dev/null +++ b/src/lib/sql/columns.sql.ts @@ -0,0 +1,129 @@ +import type { SQLQueryPropsWithSchemaFilter } from './common.js' + +export const COLUMNS_SQL = ( + props: SQLQueryPropsWithSchemaFilter & { + tableIdFilter?: string + tableIdentifierFilter?: string + columnNameFilter?: string + idsFilter?: string + } +) => /* SQL */ ` +-- Adapted from information_schema.columns + +SELECT + c.oid :: int8 AS table_id, + nc.nspname AS schema, + c.relname AS table, + (c.oid || '.' || a.attnum) AS id, + a.attnum AS ordinal_position, + a.attname AS name, + CASE + WHEN a.atthasdef THEN pg_get_expr(ad.adbin, ad.adrelid) + ELSE NULL + END AS default_value, + CASE + WHEN t.typtype = 'd' THEN CASE + WHEN bt.typelem <> 0 :: oid + AND bt.typlen = -1 THEN 'ARRAY' + WHEN nbt.nspname = 'pg_catalog' THEN format_type(t.typbasetype, NULL) + ELSE 'USER-DEFINED' + END + ELSE CASE + WHEN t.typelem <> 0 :: oid + AND t.typlen = -1 THEN 'ARRAY' + WHEN nt.nspname = 'pg_catalog' THEN format_type(a.atttypid, NULL) + ELSE 'USER-DEFINED' + END + END AS data_type, + COALESCE(bt.typname, t.typname) AS format, + a.attidentity IN ('a', 'd') AS is_identity, + CASE + a.attidentity + WHEN 'a' THEN 'ALWAYS' + WHEN 'd' THEN 'BY DEFAULT' + ELSE NULL + END AS identity_generation, + a.attgenerated IN ('s') AS is_generated, + NOT ( + a.attnotnull + OR t.typtype = 'd' AND t.typnotnull + ) AS is_nullable, + ( + c.relkind IN ('r', 'p') + OR c.relkind IN ('v', 'f') AND pg_column_is_updatable(c.oid, a.attnum, FALSE) + ) AS is_updatable, + uniques.table_id IS NOT NULL AS is_unique, + check_constraints.definition AS "check", + array_to_json( + array( + SELECT + enumlabel + FROM + pg_catalog.pg_enum enums + WHERE + enums.enumtypid = coalesce(bt.oid, t.oid) + OR enums.enumtypid = coalesce(bt.typelem, t.typelem) + ORDER BY + enums.enumsortorder + ) + ) AS enums, + col_description(c.oid, a.attnum) AS comment +FROM + pg_attribute a + LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid + AND a.attnum = ad.adnum + JOIN ( + pg_class c + JOIN pg_namespace nc ON c.relnamespace = nc.oid + ) ON a.attrelid = c.oid + JOIN ( + pg_type t + JOIN pg_namespace nt ON t.typnamespace = nt.oid + ) ON a.atttypid = t.oid + LEFT JOIN ( + pg_type bt + JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid + ) ON t.typtype = 'd' + AND t.typbasetype = bt.oid + LEFT JOIN ( + SELECT DISTINCT ON (table_id, ordinal_position) + conrelid AS table_id, + conkey[1] AS ordinal_position + FROM pg_catalog.pg_constraint + WHERE contype = 'u' AND cardinality(conkey) = 1 + ) AS uniques ON uniques.table_id = c.oid AND uniques.ordinal_position = a.attnum + LEFT JOIN ( + -- We only select the first column check + SELECT DISTINCT ON (table_id, ordinal_position) + conrelid AS table_id, + conkey[1] AS ordinal_position, + substring( + pg_get_constraintdef(pg_constraint.oid, true), + 8, + length(pg_get_constraintdef(pg_constraint.oid, true)) - 8 + ) AS "definition" + FROM pg_constraint + WHERE contype = 'c' AND cardinality(conkey) = 1 + ORDER BY table_id, ordinal_position, oid asc + ) AS check_constraints ON check_constraints.table_id = c.oid AND check_constraints.ordinal_position = a.attnum +WHERE + ${props.schemaFilter ? `nc.nspname ${props.schemaFilter} AND` : ''} + ${props.idsFilter ? `(c.oid || '.' || a.attnum) ${props.idsFilter} AND` : ''} + ${props.columnNameFilter ? `(c.relname || '.' || a.attname) ${props.columnNameFilter} AND` : ''} + ${props.tableIdFilter ? `c.oid ${props.tableIdFilter} AND` : ''} + ${props.tableIdentifierFilter ? `nc.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ''} + NOT pg_is_other_temp_schema(nc.oid) + AND a.attnum > 0 + AND NOT a.attisdropped + AND (c.relkind IN ('r', 'v', 'm', 'f', 'p')) + AND ( + pg_has_role(c.relowner, 'USAGE') + OR has_column_privilege( + c.oid, + a.attnum, + 'SELECT, INSERT, UPDATE, REFERENCES' + ) + ) +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/common.ts b/src/lib/sql/common.ts new file mode 100644 index 00000000..b9c37ec9 --- /dev/null +++ b/src/lib/sql/common.ts @@ -0,0 +1,17 @@ +export type SQLQueryProps = { + limit?: number + offset?: number +} + +export type SQLQueryPropsWithSchemaFilter = SQLQueryProps & { + schemaFilter?: string +} + +export type SQLQueryPropsWithIdsFilter = SQLQueryProps & { + idsFilter?: string +} + +export type SQLQueryPropsWithSchemaFilterAndIdsFilter = SQLQueryProps & { + schemaFilter?: string + idsFilter?: string +} diff --git a/src/lib/sql/config.sql b/src/lib/sql/config.sql.ts similarity index 57% rename from src/lib/sql/config.sql rename to src/lib/sql/config.sql.ts index 553e4426..f33305d5 100644 --- a/src/lib/sql/config.sql +++ b/src/lib/sql/config.sql.ts @@ -1,3 +1,6 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const CONFIG_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` SELECT name, setting, @@ -23,3 +26,6 @@ FROM ORDER BY category, name +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/extensions.sql b/src/lib/sql/extensions.sql deleted file mode 100644 index 9a8700f8..00000000 --- a/src/lib/sql/extensions.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - e.name, - n.nspname AS schema, - e.default_version, - x.extversion AS installed_version, - e.comment -FROM - pg_available_extensions() e(name, default_version, comment) - LEFT JOIN pg_extension x ON e.name = x.extname - LEFT JOIN pg_namespace n ON x.extnamespace = n.oid diff --git a/src/lib/sql/extensions.sql.ts b/src/lib/sql/extensions.sql.ts new file mode 100644 index 00000000..fe65b0c2 --- /dev/null +++ b/src/lib/sql/extensions.sql.ts @@ -0,0 +1,19 @@ +import type { SQLQueryProps } from './common.js' + +export const EXTENSIONS_SQL = (props: SQLQueryProps & { nameFilter?: string }) => /* SQL */ ` +SELECT + e.name, + n.nspname AS schema, + e.default_version, + x.extversion AS installed_version, + e.comment +FROM + pg_available_extensions() e(name, default_version, comment) + LEFT JOIN pg_extension x ON e.name = x.extname + LEFT JOIN pg_namespace n ON x.extnamespace = n.oid +WHERE + true + ${props.nameFilter ? `AND e.name ${props.nameFilter}` : ''} +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/foreign_tables.sql.ts b/src/lib/sql/foreign_tables.sql.ts new file mode 100644 index 00000000..00541f0f --- /dev/null +++ b/src/lib/sql/foreign_tables.sql.ts @@ -0,0 +1,25 @@ +import type { SQLQueryProps } from './common.js' + +export const FOREIGN_TABLES_SQL = ( + props: SQLQueryProps & { + schemaFilter?: string + idsFilter?: string + tableIdentifierFilter?: string + } +) => /* SQL */ ` +SELECT + c.oid :: int8 AS id, + n.nspname AS schema, + c.relname AS name, + obj_description(c.oid) AS comment +FROM + pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE + ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} + ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ''} + ${props.tableIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.tableIdentifierFilter} AND` : ''} + c.relkind = 'f' +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/functions.sql b/src/lib/sql/functions.sql deleted file mode 100644 index a6233684..00000000 --- a/src/lib/sql/functions.sql +++ /dev/null @@ -1,16 +0,0 @@ -SELECT - p.oid :: int8 AS id, - n.nspname AS schema, - p.proname AS name, - l.lanname AS language, - CASE - WHEN l.lanname = 'internal' THEN p.prosrc - ELSE pg_get_functiondef(p.oid) - END AS definition, - pg_get_function_arguments(p.oid) AS argument_types, - t.typname AS return_type -FROM - pg_proc p - LEFT JOIN pg_namespace n ON p.pronamespace = n.oid - LEFT JOIN pg_language l ON p.prolang = l.oid - LEFT JOIN pg_type t ON t.oid = p.prorettype diff --git a/src/lib/sql/functions.sql.ts b/src/lib/sql/functions.sql.ts new file mode 100644 index 00000000..97dad2f3 --- /dev/null +++ b/src/lib/sql/functions.sql.ts @@ -0,0 +1,155 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const FUNCTIONS_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + nameFilter?: string + args?: string[] + } +) => /* SQL */ ` +-- CTE with sane arg_modes, arg_names, and arg_types. +-- All three are always of the same length. +-- All three include all args, including OUT and TABLE args. +with functions as ( + select + p.*, + -- proargmodes is null when all arg modes are IN + coalesce( + p.proargmodes, + array_fill('i'::text, array[cardinality(coalesce(p.proallargtypes, p.proargtypes))]) + ) as arg_modes, + -- proargnames is null when all args are unnamed + coalesce( + p.proargnames, + array_fill(''::text, array[cardinality(coalesce(p.proallargtypes, p.proargtypes))]) + ) as arg_names, + -- proallargtypes is null when all arg modes are IN + coalesce(p.proallargtypes, p.proargtypes) as arg_types, + array_cat( + array_fill(false, array[pronargs - pronargdefaults]), + array_fill(true, array[pronargdefaults])) as arg_has_defaults + from + pg_proc as p + ${props.schemaFilter ? `join pg_namespace n on p.pronamespace = n.oid` : ''} + where + ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} + ${props.idsFilter ? `p.oid ${props.idsFilter} AND` : ''} + ${props.nameFilter ? `p.proname ${props.nameFilter} AND` : ''} + ${ + props.args === undefined + ? '' + : props.args.length > 0 + ? `p.proargtypes::text = ${ + props.args.length + ? `( + SELECT STRING_AGG(type_oid::text, ' ') FROM ( + SELECT ( + split_args.arr[ + array_length( + split_args.arr, + 1 + ) + ]::regtype::oid + ) AS type_oid FROM ( + SELECT STRING_TO_ARRAY( + UNNEST( + ARRAY[${props.args}] + ), + ' ' + ) AS arr + ) AS split_args + ) args + )` + : "''" + } AND` + : '' + } + p.prokind = 'f' +) +select + f.oid::int8 as id, + n.nspname as schema, + f.proname as name, + l.lanname as language, + case + when l.lanname = 'internal' then '' + else f.prosrc + end as definition, + case + when l.lanname = 'internal' then f.prosrc + else pg_get_functiondef(f.oid) + end as complete_statement, + coalesce(f_args.args, '[]') as args, + pg_get_function_arguments(f.oid) as argument_types, + pg_get_function_identity_arguments(f.oid) as identity_argument_types, + f.prorettype::int8 as return_type_id, + pg_get_function_result(f.oid) as return_type, + nullif(rt.typrelid::int8, 0) as return_type_relation_id, + f.proretset as is_set_returning_function, + case + when f.proretset then nullif(f.prorows, 0) + else null + end as prorows, + case + when f.provolatile = 'i' then 'IMMUTABLE' + when f.provolatile = 's' then 'STABLE' + when f.provolatile = 'v' then 'VOLATILE' + end as behavior, + f.prosecdef as security_definer, + f_config.config_params as config_params +from + functions f + left join pg_namespace n on f.pronamespace = n.oid + left join pg_language l on f.prolang = l.oid + left join pg_type rt on rt.oid = f.prorettype + left join ( + select + oid, + jsonb_object_agg(param, value) filter (where param is not null) as config_params + from + ( + select + oid, + (string_to_array(unnest(proconfig), '='))[1] as param, + (string_to_array(unnest(proconfig), '='))[2] as value + from + functions + ) as t + group by + oid + ) f_config on f_config.oid = f.oid + left join ( + select + oid, + jsonb_agg(jsonb_build_object( + 'mode', t2.mode, + 'name', name, + 'type_id', type_id, + 'has_default', has_default + )) as args + from + ( + select + oid, + unnest(arg_modes) as mode, + unnest(arg_names) as name, + unnest(arg_types)::int8 as type_id, + unnest(arg_has_defaults) as has_default + from + functions + ) as t1, + lateral ( + select + case + when t1.mode = 'i' then 'in' + when t1.mode = 'o' then 'out' + when t1.mode = 'b' then 'inout' + when t1.mode = 'v' then 'variadic' + else 'table' + end as mode + ) as t2 + group by + t1.oid + ) f_args on f_args.oid = f.oid +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/grants.sql b/src/lib/sql/grants.sql deleted file mode 100644 index 0a8572bc..00000000 --- a/src/lib/sql/grants.sql +++ /dev/null @@ -1,104 +0,0 @@ --- Adapted from information_schema.role_table_grants - -SELECT - c.oid :: int8 AS table_id, - u_grantor.rolname AS grantor, - grantee.rolname AS grantee, - nc.nspname AS schema, - c.relname AS table_name, - c.prtype AS privilege_type, - CASE - WHEN pg_has_role(grantee.oid, c.relowner, 'USAGE') - OR c.grantable THEN TRUE - ELSE FALSE - END AS is_grantable, - CASE - WHEN c.prtype = 'SELECT' THEN TRUE - ELSE FALSE - END AS with_hierarchy -FROM - ( - SELECT - pg_class.oid, - pg_class.relname, - pg_class.relnamespace, - pg_class.relkind, - pg_class.relowner, - ( - aclexplode( - COALESCE( - pg_class.relacl, - acldefault('r', pg_class.relowner) - ) - ) - ).grantor AS grantor, - ( - aclexplode( - COALESCE( - pg_class.relacl, - acldefault('r', pg_class.relowner) - ) - ) - ).grantee AS grantee, - ( - aclexplode( - COALESCE( - pg_class.relacl, - acldefault('r', pg_class.relowner) - ) - ) - ).privilege_type AS privilege_type, - ( - aclexplode( - COALESCE( - pg_class.relacl, - acldefault('r', pg_class.relowner) - ) - ) - ).is_grantable AS is_grantable - FROM - pg_class - ) c( - oid, - relname, - relnamespace, - relkind, - relowner, - grantor, - grantee, - prtype, - grantable - ), - pg_namespace nc, - pg_authid u_grantor, - ( - SELECT - pg_authid.oid, - pg_authid.rolname - FROM - pg_authid - UNION ALL - SELECT - 0 :: oid AS oid, - 'PUBLIC' - ) grantee(oid, rolname) -WHERE - c.relnamespace = nc.oid - AND (c.relkind IN ('r', 'v', 'f', 'p')) - AND c.grantee = grantee.oid - AND c.grantor = u_grantor.oid - AND ( - c.prtype IN ( - 'INSERT', - 'SELECT', - 'UPDATE', - 'DELETE', - 'TRUNCATE', - 'REFERENCES', - 'TRIGGER' - ) - ) - AND ( - pg_has_role(u_grantor.oid, 'USAGE') - OR pg_has_role(grantee.oid, 'USAGE') - ) diff --git a/src/lib/sql/index.ts b/src/lib/sql/index.ts deleted file mode 100644 index 4ec395f9..00000000 --- a/src/lib/sql/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { readFileSync } from 'fs' -import { resolve } from 'path' - -export const columnsSql = readFileSync(resolve(__dirname, 'columns.sql'), 'utf-8') -export const configSql = readFileSync(resolve(__dirname, 'config.sql'), 'utf-8') -export const extensionsSql = readFileSync(resolve(__dirname, 'extensions.sql'), 'utf-8') -export const functionsSql = readFileSync(resolve(__dirname, 'functions.sql'), 'utf-8') -export const grantsSql = readFileSync(resolve(__dirname, 'grants.sql'), 'utf-8') -export const policiesSql = readFileSync(resolve(__dirname, 'policies.sql'), 'utf-8') -export const primaryKeysSql = readFileSync(resolve(__dirname, 'primary_keys.sql'), 'utf-8') -export const publicationsSql = readFileSync(resolve(__dirname, 'publications.sql'), 'utf-8') -export const relationshipsSql = readFileSync(resolve(__dirname, 'relationships.sql'), 'utf-8') -export const rolesSql = readFileSync(resolve(__dirname, 'roles.sql'), 'utf-8') -export const schemasSql = readFileSync(resolve(__dirname, 'schemas.sql'), 'utf-8') -export const tablesSql = readFileSync(resolve(__dirname, 'tables.sql'), 'utf-8') -export const triggersSql = readFileSync(resolve(__dirname, 'triggers.sql'), 'utf-8') -export const typesSql = readFileSync(resolve(__dirname, 'types.sql'), 'utf-8') -export const versionSql = readFileSync(resolve(__dirname, 'version.sql'), 'utf-8') diff --git a/src/lib/sql/indexes.sql.ts b/src/lib/sql/indexes.sql.ts new file mode 100644 index 00000000..5f893a8f --- /dev/null +++ b/src/lib/sql/indexes.sql.ts @@ -0,0 +1,50 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const INDEXES_SQL = (props: SQLQueryPropsWithSchemaFilterAndIdsFilter) => /* SQL */ ` +SELECT + idx.indexrelid::int8 AS id, + idx.indrelid::int8 AS table_id, + n.nspname AS schema, + idx.indnatts AS number_of_attributes, + idx.indnkeyatts AS number_of_key_attributes, + idx.indisunique AS is_unique, + idx.indisprimary AS is_primary, + idx.indisexclusion AS is_exclusion, + idx.indimmediate AS is_immediate, + idx.indisclustered AS is_clustered, + idx.indisvalid AS is_valid, + idx.indcheckxmin AS check_xmin, + idx.indisready AS is_ready, + idx.indislive AS is_live, + idx.indisreplident AS is_replica_identity, + idx.indkey::smallint[] AS key_attributes, + idx.indcollation::integer[] AS collation, + idx.indclass::integer[] AS class, + idx.indoption::smallint[] AS options, + idx.indpred AS index_predicate, + obj_description(idx.indexrelid, 'pg_class') AS comment, + ix.indexdef as index_definition, + am.amname AS access_method, + jsonb_agg( + jsonb_build_object( + 'attribute_number', a.attnum, + 'attribute_name', a.attname, + 'data_type', format_type(a.atttypid, a.atttypmod) + ) + ORDER BY a.attnum + ) AS index_attributes + FROM + pg_index idx + JOIN pg_class c ON c.oid = idx.indexrelid + JOIN pg_namespace n ON c.relnamespace = n.oid + JOIN pg_am am ON c.relam = am.oid + JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(idx.indkey) + JOIN pg_indexes ix ON c.relname = ix.indexname + WHERE + ${props.schemaFilter ? `n.nspname ${props.schemaFilter}` : 'true'} + ${props.idsFilter ? `AND idx.indexrelid ${props.idsFilter}` : ''} + GROUP BY + idx.indexrelid, idx.indrelid, n.nspname, idx.indnatts, idx.indnkeyatts, idx.indisunique, idx.indisprimary, idx.indisexclusion, idx.indimmediate, idx.indisclustered, idx.indisvalid, idx.indcheckxmin, idx.indisready, idx.indislive, idx.indisreplident, idx.indkey, idx.indcollation, idx.indclass, idx.indoption, idx.indexprs, idx.indpred, ix.indexdef, am.amname +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/materialized_views.sql.ts b/src/lib/sql/materialized_views.sql.ts new file mode 100644 index 00000000..aae179e8 --- /dev/null +++ b/src/lib/sql/materialized_views.sql.ts @@ -0,0 +1,24 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const MATERIALIZED_VIEWS_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + materializedViewIdentifierFilter?: string + } +) => /* SQL */ ` +select + c.oid::int8 as id, + n.nspname as schema, + c.relname as name, + c.relispopulated as is_populated, + obj_description(c.oid) as comment +from + pg_class c + join pg_namespace n on n.oid = c.relnamespace +where + ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} + ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ''} + ${props.materializedViewIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.materializedViewIdentifierFilter} AND` : ''} + c.relkind = 'm' +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/policies.sql b/src/lib/sql/policies.sql.ts similarity index 59% rename from src/lib/sql/policies.sql rename to src/lib/sql/policies.sql.ts index 1e92840f..9e354931 100644 --- a/src/lib/sql/policies.sql +++ b/src/lib/sql/policies.sql.ts @@ -1,3 +1,8 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const POLICIES_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { functionNameIdentifierFilter?: string } +) => /* SQL */ ` SELECT pol.oid :: int8 AS id, n.nspname AS schema, @@ -15,13 +20,13 @@ SELECT ELSE array_to_json( ARRAY( SELECT - pg_authid.rolname + pg_roles.rolname FROM - pg_authid + pg_roles WHERE - pg_authid.oid = ANY (pol.polroles) + pg_roles.oid = ANY (pol.polroles) ORDER BY - pg_authid.rolname + pg_roles.rolname ) ) END AS roles, @@ -40,3 +45,10 @@ FROM pg_policy pol JOIN pg_class c ON c.oid = pol.polrelid LEFT JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE + ${props.schemaFilter ? `n.nspname ${props.schemaFilter}` : 'true'} + ${props.idsFilter ? `AND pol.oid ${props.idsFilter}` : ''} + ${props.functionNameIdentifierFilter ? `AND (c.relname || '.' || pol.polname) ${props.functionNameIdentifierFilter}` : ''} +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/primary_keys.sql b/src/lib/sql/primary_keys.sql deleted file mode 100644 index b49d0603..00000000 --- a/src/lib/sql/primary_keys.sql +++ /dev/null @@ -1,16 +0,0 @@ -SELECT - n.nspname AS schema, - c.relname AS table_name, - a.attname AS name, - c.oid :: int8 AS table_id -FROM - pg_index i, - pg_class c, - pg_attribute a, - pg_namespace n -WHERE - i.indrelid = c.oid - AND c.relnamespace = n.oid - AND a.attrelid = c.oid - AND a.attnum = ANY (i.indkey) - AND i.indisprimary diff --git a/src/lib/sql/publications.sql b/src/lib/sql/publications.sql.ts similarity index 64% rename from src/lib/sql/publications.sql rename to src/lib/sql/publications.sql.ts index e8a925e4..cd04e05b 100644 --- a/src/lib/sql/publications.sql +++ b/src/lib/sql/publications.sql.ts @@ -1,7 +1,12 @@ +import type { SQLQueryPropsWithIdsFilter } from './common.js' + +export const PUBLICATIONS_SQL = ( + props: SQLQueryPropsWithIdsFilter & { nameFilter?: string } +) => /* SQL */ ` SELECT p.oid :: int8 AS id, p.pubname AS name, - r.rolname AS owner, + p.pubowner::regrole::text AS owner, p.pubinsert AS publish_insert, p.pubupdate AS publish_update, p.pubdelete AS publish_delete, @@ -34,4 +39,9 @@ FROM WHERE pr.prpubid = p.oid ) AS pr ON 1 = 1 - JOIN pg_roles AS r ON p.pubowner = r.oid +WHERE + ${props.idsFilter ? `p.oid ${props.idsFilter}` : 'true'} + ${props.nameFilter ? `AND p.pubname ${props.nameFilter}` : ''} +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/relationships.sql b/src/lib/sql/relationships.sql deleted file mode 100644 index 953a33e3..00000000 --- a/src/lib/sql/relationships.sql +++ /dev/null @@ -1,25 +0,0 @@ -SELECT - c.oid :: int8 AS id, - c.conname AS constraint_name, - nsa.nspname AS source_schema, - csa.relname AS source_table_name, - sa.attname AS source_column_name, - nta.nspname AS target_table_schema, - cta.relname AS target_table_name, - ta.attname AS target_column_name -FROM - pg_constraint c - JOIN ( - pg_attribute sa - JOIN pg_class csa ON sa.attrelid = csa.oid - JOIN pg_namespace nsa ON csa.relnamespace = nsa.oid - ) ON sa.attrelid = c.conrelid - AND sa.attnum = ANY (c.conkey) - JOIN ( - pg_attribute ta - JOIN pg_class cta ON ta.attrelid = cta.oid - JOIN pg_namespace nta ON cta.relnamespace = nta.oid - ) ON ta.attrelid = c.confrelid - AND ta.attnum = ANY (c.confkey) -WHERE - c.contype = 'f' diff --git a/src/lib/sql/roles.sql b/src/lib/sql/roles.sql.ts similarity index 52% rename from src/lib/sql/roles.sql rename to src/lib/sql/roles.sql.ts index a0c79d6f..b3d29358 100644 --- a/src/lib/sql/roles.sql +++ b/src/lib/sql/roles.sql.ts @@ -1,3 +1,11 @@ +import type { SQLQueryPropsWithIdsFilter } from './common.js' + +export const ROLES_SQL = ( + props: SQLQueryPropsWithIdsFilter & { + includeDefaultRoles?: boolean + nameFilter?: string + } +) => /* SQL */ ` -- TODO: Consider using pg_authid vs. pg_roles for unencrypted password field SELECT oid :: int8 AS id, @@ -25,3 +33,12 @@ SELECT rolconfig AS config FROM pg_roles +WHERE + ${props.idsFilter ? `oid ${props.idsFilter}` : 'true'} + -- All default/predefined roles start with pg_: https://www.postgresql.org/docs/15/predefined-roles.html + -- The pg_ prefix is also reserved. + ${!props.includeDefaultRoles ? `AND NOT pg_catalog.starts_with(rolname, 'pg_')` : ''} + ${props.nameFilter ? `AND rolname ${props.nameFilter}` : ''} +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/schemas.sql b/src/lib/sql/schemas.sql deleted file mode 100644 index 6ba96808..00000000 --- a/src/lib/sql/schemas.sql +++ /dev/null @@ -1,15 +0,0 @@ --- Adapted from information_schema.schemata - -SELECT - n.oid :: int8 AS id, - n.nspname AS name, - u.rolname AS owner -FROM - pg_namespace n, - pg_authid u -WHERE - n.nspowner = u.oid - AND ( - pg_has_role(n.nspowner, 'USAGE') - OR has_schema_privilege(n.oid, 'CREATE, USAGE') - ) diff --git a/src/lib/sql/schemas.sql.ts b/src/lib/sql/schemas.sql.ts new file mode 100644 index 00000000..a9e5d85b --- /dev/null +++ b/src/lib/sql/schemas.sql.ts @@ -0,0 +1,27 @@ +import type { SQLQueryProps } from './common.js' + +export const SCHEMAS_SQL = ( + props: SQLQueryProps & { nameFilter?: string; idsFilter?: string; includeSystemSchemas?: boolean } +) => /* SQL */ ` +-- Adapted from information_schema.schemata +select + n.oid::int8 as id, + n.nspname as name, + u.rolname as owner +from + pg_namespace n, + pg_roles u +where + n.nspowner = u.oid + ${props.idsFilter ? `and n.oid ${props.idsFilter}` : ''} + ${props.nameFilter ? `and n.nspname ${props.nameFilter}` : ''} + ${!props.includeSystemSchemas ? `and not pg_catalog.starts_with(n.nspname, 'pg_')` : ''} + and ( + pg_has_role(n.nspowner, 'USAGE') + or has_schema_privilege(n.oid, 'CREATE, USAGE') + ) + and not pg_catalog.starts_with(n.nspname, 'pg_temp_') + and not pg_catalog.starts_with(n.nspname, 'pg_toast_temp_') +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/table.sql.ts b/src/lib/sql/table.sql.ts new file mode 100644 index 00000000..446c1f40 --- /dev/null +++ b/src/lib/sql/table.sql.ts @@ -0,0 +1,110 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const TABLES_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { tableIdentifierFilter?: string } +) => /* SQL */ ` +SELECT + c.oid :: int8 AS id, + nc.nspname AS schema, + c.relname AS name, + c.relrowsecurity AS rls_enabled, + c.relforcerowsecurity AS rls_forced, + CASE + WHEN c.relreplident = 'd' THEN 'DEFAULT' + WHEN c.relreplident = 'i' THEN 'INDEX' + WHEN c.relreplident = 'f' THEN 'FULL' + ELSE 'NOTHING' + END AS replica_identity, + pg_total_relation_size(format('%I.%I', nc.nspname, c.relname)) :: int8 AS bytes, + pg_size_pretty( + pg_total_relation_size(format('%I.%I', nc.nspname, c.relname)) + ) AS size, + pg_stat_get_live_tuples(c.oid) AS live_rows_estimate, + pg_stat_get_dead_tuples(c.oid) AS dead_rows_estimate, + obj_description(c.oid) AS comment, + coalesce(pk.primary_keys, '[]') as primary_keys, + coalesce( + jsonb_agg(relationships) filter (where relationships is not null), + '[]' + ) as relationships +FROM + pg_namespace nc + JOIN pg_class c ON nc.oid = c.relnamespace + left join ( + select + c.oid::int8 as table_id, + jsonb_agg( + jsonb_build_object( + 'table_id', c.oid::int8, + 'schema', n.nspname, + 'table_name', c.relname, + 'name', a.attname + ) + order by array_position(i.indkey, a.attnum) + ) as primary_keys + from + pg_index i + join pg_class c on i.indrelid = c.oid + join pg_namespace n on c.relnamespace = n.oid + join pg_attribute a on a.attrelid = c.oid and a.attnum = any(i.indkey) + where + ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} + ${props.tableIdentifierFilter ? `n.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ''} + i.indisprimary + group by c.oid + ) as pk + on pk.table_id = c.oid + left join ( + select + c.oid :: int8 as id, + c.conname as constraint_name, + nsa.nspname as source_schema, + csa.relname as source_table_name, + sa.attname as source_column_name, + nta.nspname as target_table_schema, + cta.relname as target_table_name, + ta.attname as target_column_name + from + pg_constraint c + join ( + pg_attribute sa + join pg_class csa on sa.attrelid = csa.oid + join pg_namespace nsa on csa.relnamespace = nsa.oid + ) on sa.attrelid = c.conrelid and sa.attnum = any (c.conkey) + join ( + pg_attribute ta + join pg_class cta on ta.attrelid = cta.oid + join pg_namespace nta on cta.relnamespace = nta.oid + ) on ta.attrelid = c.confrelid and ta.attnum = any (c.confkey) + where + ${props.schemaFilter ? `nsa.nspname ${props.schemaFilter} OR nta.nspname ${props.schemaFilter} AND` : ''} + ${props.tableIdentifierFilter ? `(nsa.nspname || '.' || csa.relname) ${props.tableIdentifierFilter} OR (nta.nspname || '.' || cta.relname) ${props.tableIdentifierFilter} AND` : ''} + c.contype = 'f' + ) as relationships + on (relationships.source_schema = nc.nspname and relationships.source_table_name = c.relname) + or (relationships.target_table_schema = nc.nspname and relationships.target_table_name = c.relname) +WHERE + ${props.schemaFilter ? `nc.nspname ${props.schemaFilter} AND` : ''} + ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ''} + ${props.tableIdentifierFilter ? `nc.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ''} + c.relkind IN ('r', 'p') + AND NOT pg_is_other_temp_schema(nc.oid) + AND ( + pg_has_role(c.relowner, 'USAGE') + OR has_table_privilege( + c.oid, + 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER' + ) + OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES') + ) +group by + c.oid, + c.relname, + c.relrowsecurity, + c.relforcerowsecurity, + c.relreplident, + nc.nspname, + pk.primary_keys +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/table_privileges.sql.ts b/src/lib/sql/table_privileges.sql.ts new file mode 100644 index 00000000..ca4ea122 --- /dev/null +++ b/src/lib/sql/table_privileges.sql.ts @@ -0,0 +1,88 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const TABLE_PRIVILEGES_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + nameIdentifierFilter?: string + } +) => /* SQL */ ` +-- Despite the name \`table_privileges\`, this includes other kinds of relations: +-- views, matviews, etc. "Relation privileges" just doesn't roll off the tongue. +-- +-- For each relation, get its relacl in a jsonb format, +-- e.g. +-- +-- '{postgres=arwdDxt/postgres}' +-- +-- becomes +-- +-- [ +-- { +-- "grantee": "postgres", +-- "grantor": "postgres", +-- "is_grantable": false, +-- "privilege_type": "INSERT" +-- }, +-- ... +-- ] +select + c.oid as relation_id, + nc.nspname as schema, + c.relname as name, + case + when c.relkind = 'r' then 'table' + when c.relkind = 'v' then 'view' + when c.relkind = 'm' then 'materialized_view' + when c.relkind = 'f' then 'foreign_table' + when c.relkind = 'p' then 'partitioned_table' + end as kind, + coalesce( + jsonb_agg( + jsonb_build_object( + 'grantor', grantor.rolname, + 'grantee', grantee.rolname, + 'privilege_type', _priv.privilege_type, + 'is_grantable', _priv.is_grantable + ) + ) filter (where _priv is not null), + '[]' + ) as privileges +from pg_class c +join pg_namespace as nc + on nc.oid = c.relnamespace +left join lateral ( + select grantor, grantee, privilege_type, is_grantable + from aclexplode(coalesce(c.relacl, acldefault('r', c.relowner))) +) as _priv on true +left join pg_roles as grantor + on grantor.oid = _priv.grantor +left join ( + select + pg_roles.oid, + pg_roles.rolname + from pg_roles + union all + select + (0)::oid as oid, 'PUBLIC' +) as grantee (oid, rolname) + on grantee.oid = _priv.grantee +where c.relkind in ('r', 'v', 'm', 'f', 'p') + ${props.schemaFilter ? `and nc.nspname ${props.schemaFilter}` : ''} + ${props.idsFilter ? `and c.oid ${props.idsFilter}` : ''} + ${props.nameIdentifierFilter ? `and (nc.nspname || '.' || c.relname) ${props.nameIdentifierFilter}` : ''} + and not pg_is_other_temp_schema(c.relnamespace) + and ( + pg_has_role(c.relowner, 'USAGE') + or has_table_privilege( + c.oid, + 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, MAINTAIN' + ) + or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES') + ) +group by + c.oid, + nc.nspname, + c.relname, + c.relkind +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/table_relationships.sql.ts b/src/lib/sql/table_relationships.sql.ts new file mode 100644 index 00000000..d74c12a4 --- /dev/null +++ b/src/lib/sql/table_relationships.sql.ts @@ -0,0 +1,51 @@ +import type { SQLQueryPropsWithSchemaFilter } from './common.js' + +export const TABLE_RELATIONSHIPS_SQL = (props: SQLQueryPropsWithSchemaFilter) => /* SQL */ ` +-- Adapted from +-- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L722 +WITH +pks_uniques_cols AS ( + SELECT + connamespace, + conrelid, + jsonb_agg(column_info.cols) as cols + FROM pg_constraint + JOIN lateral ( + SELECT array_agg(cols.attname order by cols.attnum) as cols + FROM ( select unnest(conkey) as col) _ + JOIN pg_attribute cols on cols.attrelid = conrelid and cols.attnum = col + ) column_info ON TRUE + WHERE + contype IN ('p', 'u') and + connamespace::regnamespace::text <> 'pg_catalog' + ${props.schemaFilter ? `and connamespace::regnamespace::text ${props.schemaFilter}` : ''} + GROUP BY connamespace, conrelid +) +SELECT + traint.conname AS foreign_key_name, + ns1.nspname AS schema, + tab.relname AS relation, + column_info.cols AS columns, + ns2.nspname AS referenced_schema, + other.relname AS referenced_relation, + column_info.refs AS referenced_columns, + (column_info.cols IN (SELECT * FROM jsonb_array_elements(pks_uqs.cols))) AS is_one_to_one +FROM pg_constraint traint +JOIN LATERAL ( + SELECT + jsonb_agg(cols.attname order by ord) AS cols, + jsonb_agg(refs.attname order by ord) AS refs + FROM unnest(traint.conkey, traint.confkey) WITH ORDINALITY AS _(col, ref, ord) + JOIN pg_attribute cols ON cols.attrelid = traint.conrelid AND cols.attnum = col + JOIN pg_attribute refs ON refs.attrelid = traint.confrelid AND refs.attnum = ref + WHERE ${props.schemaFilter ? `traint.connamespace::regnamespace::text ${props.schemaFilter}` : 'true'} +) AS column_info ON TRUE +JOIN pg_namespace ns1 ON ns1.oid = traint.connamespace +JOIN pg_class tab ON tab.oid = traint.conrelid +JOIN pg_class other ON other.oid = traint.confrelid +JOIN pg_namespace ns2 ON ns2.oid = other.relnamespace +LEFT JOIN pks_uniques_cols pks_uqs ON pks_uqs.connamespace = traint.connamespace AND pks_uqs.conrelid = traint.conrelid +WHERE traint.contype = 'f' +AND traint.conparentid = 0 +${props.schemaFilter ? `and ns1.nspname ${props.schemaFilter}` : ''} +` diff --git a/src/lib/sql/tables.sql b/src/lib/sql/tables.sql deleted file mode 100644 index 5ac269fb..00000000 --- a/src/lib/sql/tables.sql +++ /dev/null @@ -1,33 +0,0 @@ -SELECT - c.oid :: int8 AS id, - nc.nspname AS schema, - c.relname AS name, - c.relrowsecurity AS rls_enabled, - c.relforcerowsecurity AS rls_forced, - CASE - WHEN c.relreplident = 'd' THEN 'DEFAULT' - WHEN c.relreplident = 'i' THEN 'INDEX' - WHEN c.relreplident = 'f' THEN 'FULL' - ELSE 'NOTHING' - END AS replica_identity, - pg_total_relation_size(format('%I.%I', nc.nspname, c.relname)) :: int8 AS bytes, - pg_size_pretty( - pg_total_relation_size(format('%I.%I', nc.nspname, c.relname)) - ) AS size, - pg_stat_get_live_tuples(c.oid) AS live_rows_estimate, - pg_stat_get_dead_tuples(c.oid) AS dead_rows_estimate, - obj_description(c.oid) AS comment -FROM - pg_namespace nc - JOIN pg_class c ON nc.oid = c.relnamespace -WHERE - c.relkind IN ('r', 'p') - AND NOT pg_is_other_temp_schema(nc.oid) - AND ( - pg_has_role(c.relowner, 'USAGE') - OR has_table_privilege( - c.oid, - 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER' - ) - OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES') - ) diff --git a/src/lib/sql/triggers.sql b/src/lib/sql/triggers.sql.ts similarity index 56% rename from src/lib/sql/triggers.sql rename to src/lib/sql/triggers.sql.ts index 55b9fcaa..5580373e 100644 --- a/src/lib/sql/triggers.sql +++ b/src/lib/sql/triggers.sql.ts @@ -1,14 +1,23 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const TRIGGERS_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + tableNameFilter?: string + nameFilter?: string + } +) => /* SQL */ ` SELECT pg_t.oid AS id, + pg_t.tgrelid AS table_id, CASE WHEN pg_t.tgenabled = 'D' THEN 'DISABLED' WHEN pg_t.tgenabled = 'O' THEN 'ORIGIN' WHEN pg_t.tgenabled = 'R' THEN 'REPLICA' WHEN pg_t.tgenabled = 'A' THEN 'ALWAYS' - END AS enabled_mode, + END AS enabled_mode, ( STRING_TO_ARRAY( - ENCODE(pg_t.tgargs, 'escape'), '\000' + ENCODE(pg_t.tgargs, 'escape'), '\\000' ) )[:pg_t.tgnargs] AS function_args, is_t.trigger_name AS name, @@ -25,15 +34,24 @@ FROM JOIN pg_class AS pg_c ON pg_t.tgrelid = pg_c.oid +JOIN pg_namespace AS table_ns +ON pg_c.relnamespace = table_ns.oid JOIN information_schema.triggers AS is_t ON is_t.trigger_name = pg_t.tgname AND pg_c.relname = is_t.event_object_table +AND pg_c.relnamespace = (quote_ident(is_t.event_object_schema))::regnamespace JOIN pg_proc AS pg_p ON pg_t.tgfoid = pg_p.oid JOIN pg_namespace AS pg_n ON pg_p.pronamespace = pg_n.oid +WHERE + ${props.schemaFilter ? `table_ns.nspname ${props.schemaFilter}` : 'true'} + ${props.tableNameFilter ? `AND pg_c.relname ${props.tableNameFilter}` : ''} + ${props.nameFilter ? `AND is_t.trigger_name ${props.nameFilter}` : ''} + ${props.idsFilter ? `AND pg_t.oid ${props.idsFilter}` : ''} GROUP BY pg_t.oid, + pg_t.tgrelid, pg_t.tgenabled, pg_t.tgargs, pg_t.tgnargs, @@ -45,3 +63,6 @@ GROUP BY is_t.action_timing, pg_p.proname, pg_n.nspname +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/types.sql b/src/lib/sql/types.sql deleted file mode 100644 index ba0551d5..00000000 --- a/src/lib/sql/types.sql +++ /dev/null @@ -1,42 +0,0 @@ -SELECT - t.oid :: int8 AS id, - t.typname AS name, - n.nspname AS schema, - format_type (t.oid, NULL) AS format, - array_to_json( - array( - SELECT - e.enumlabel - FROM - pg_enum e - WHERE - e.enumtypid = t.oid - ORDER BY - e.oid - ) - ) AS enums, - obj_description (t.oid, 'pg_type') AS comment -FROM - pg_type t - LEFT JOIN pg_namespace n ON n.oid = t.typnamespace -WHERE - ( - t.typrelid = 0 - OR ( - SELECT - c.relkind = 'c' - FROM - pg_class c - WHERE - c.oid = t.typrelid - ) - ) - AND NOT EXISTS ( - SELECT - 1 - FROM - pg_type el - WHERE - el.oid = t.typelem - AND el.typarray = t.oid - ) diff --git a/src/lib/sql/types.sql.ts b/src/lib/sql/types.sql.ts new file mode 100644 index 00000000..cc94ba54 --- /dev/null +++ b/src/lib/sql/types.sql.ts @@ -0,0 +1,73 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const TYPES_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + includeTableTypes?: boolean + includeArrayTypes?: boolean + } +) => /* SQL */ ` +select + t.oid::int8 as id, + t.typname as name, + n.nspname as schema, + format_type (t.oid, null) as format, + coalesce(t_enums.enums, '[]') as enums, + coalesce(t_attributes.attributes, '[]') as attributes, + obj_description (t.oid, 'pg_type') as comment, + nullif(t.typrelid::int8, 0) as type_relation_id +from + pg_type t + left join pg_namespace n on n.oid = t.typnamespace + left join ( + select + enumtypid, + jsonb_agg(enumlabel order by enumsortorder) as enums + from + pg_enum + group by + enumtypid + ) as t_enums on t_enums.enumtypid = t.oid + left join ( + select + oid, + jsonb_agg( + jsonb_build_object('name', a.attname, 'type_id', a.atttypid::int8) + order by a.attnum asc + ) as attributes + from + pg_class c + join pg_attribute a on a.attrelid = c.oid + where + c.relkind = 'c' and not a.attisdropped + group by + c.oid + ) as t_attributes on t_attributes.oid = t.typrelid + where + ( + t.typrelid = 0 + or ( + select + c.relkind ${props.includeTableTypes ? `in ('c', 'r', 'v', 'm', 'p')` : `= 'c'`} + from + pg_class c + where + c.oid = t.typrelid + ) + ) + ${ + !props.includeArrayTypes + ? `and not exists ( + select + from + pg_type el + where + el.oid = t.typelem + and el.typarray = t.oid + )` + : '' + } + ${props.schemaFilter ? `and n.nspname ${props.schemaFilter}` : ''} + ${props.idsFilter ? `and t.oid ${props.idsFilter}` : ''} +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/version.sql b/src/lib/sql/version.sql.ts similarity index 84% rename from src/lib/sql/version.sql rename to src/lib/sql/version.sql.ts index ed7fab7e..f959c5fd 100644 --- a/src/lib/sql/version.sql +++ b/src/lib/sql/version.sql.ts @@ -1,3 +1,4 @@ +export const VERSION_SQL = () => /* SQL */ ` SELECT version(), current_setting('server_version_num') :: int8 AS version_number, @@ -8,3 +9,4 @@ SELECT pg_stat_activity ) AS active_connections, current_setting('max_connections') :: int8 AS max_connections +` diff --git a/src/lib/sql/views.sql.ts b/src/lib/sql/views.sql.ts new file mode 100644 index 00000000..95a707e2 --- /dev/null +++ b/src/lib/sql/views.sql.ts @@ -0,0 +1,25 @@ +import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from './common.js' + +export const VIEWS_SQL = ( + props: SQLQueryPropsWithSchemaFilterAndIdsFilter & { + viewIdentifierFilter?: string + } +) => /* SQL */ ` +SELECT + c.oid :: int8 AS id, + n.nspname AS schema, + c.relname AS name, + -- See definition of information_schema.views + (pg_relation_is_updatable(c.oid, false) & 20) = 20 AS is_updatable, + obj_description(c.oid) AS comment +FROM + pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE + ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ''} + ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ''} + ${props.viewIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.viewIdentifierFilter} AND` : ''} + c.relkind = 'v' +${props.limit ? `limit ${props.limit}` : ''} +${props.offset ? `offset ${props.offset}` : ''} +` diff --git a/src/lib/sql/views_key_dependencies.sql.ts b/src/lib/sql/views_key_dependencies.sql.ts new file mode 100644 index 00000000..31035012 --- /dev/null +++ b/src/lib/sql/views_key_dependencies.sql.ts @@ -0,0 +1,197 @@ +import type { SQLQueryPropsWithSchemaFilter } from './common.js' + +export const VIEWS_KEY_DEPENDENCIES_SQL = (props: SQLQueryPropsWithSchemaFilter) => /* SQL */ ` +-- Adapted from +-- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L820 +with recursive +pks_fks as ( + -- pk + fk referencing col + select + contype::text as contype, + conname, + array_length(conkey, 1) as ncol, + conrelid as resorigtbl, + col as resorigcol, + ord + from pg_constraint + left join lateral unnest(conkey) with ordinality as _(col, ord) on true + where contype IN ('p', 'f') + union + -- fk referenced col + select + concat(contype, '_ref') as contype, + conname, + array_length(confkey, 1) as ncol, + confrelid, + col, + ord + from pg_constraint + left join lateral unnest(confkey) with ordinality as _(col, ord) on true + where contype='f' + ${props.schemaFilter ? `and connamespace::regnamespace::text ${props.schemaFilter}` : ''} +), +views as ( + select + c.oid as view_id, + n.nspname as view_schema, + c.relname as view_name, + r.ev_action as view_definition + from pg_class c + join pg_namespace n on n.oid = c.relnamespace + join pg_rewrite r on r.ev_class = c.oid + where c.relkind in ('v', 'm') + ${props.schemaFilter ? `and n.nspname ${props.schemaFilter}` : ''} +), +transform_json as ( + select + view_id, view_schema, view_name, + -- the following formatting is without indentation on purpose + -- to allow simple diffs, with less whitespace noise + replace( + replace( + replace( + replace( + replace( + replace( + replace( + regexp_replace( + replace( + replace( + replace( + replace( + replace( + replace( + replace( + replace( + replace( + replace( + replace( + view_definition::text, + -- This conversion to json is heavily optimized for performance. + -- The general idea is to use as few regexp_replace() calls as possible. + -- Simple replace() is a lot faster, so we jump through some hoops + -- to be able to use regexp_replace() only once. + -- This has been tested against a huge schema with 250+ different views. + -- The unit tests do NOT reflect all possible inputs. Be careful when changing this! + -- ----------------------------------------------- + -- pattern | replacement | flags + -- ----------------------------------------------- + -- <> in pg_node_tree is the same as null in JSON, but due to very poor performance of json_typeof + -- we need to make this an empty array here to prevent json_array_elements from throwing an error + -- when the targetList is null. + -- We'll need to put it first, to make the node protection below work for node lists that start with + -- null: (<> ..., too. This is the case for coldefexprs, when the first column does not have a default value. + '<>' , '()' + -- , is not part of the pg_node_tree format, but used in the regex. + -- This removes all , that might be part of column names. + ), ',' , '' + -- The same applies for { and }, although those are used a lot in pg_node_tree. + -- We remove the escaped ones, which might be part of column names again. + ), E'\\\\{' , '' + ), E'\\\\}' , '' + -- The fields we need are formatted as json manually to protect them from the regex. + ), ' :targetList ' , ',"targetList":' + ), ' :resno ' , ',"resno":' + ), ' :resorigtbl ' , ',"resorigtbl":' + ), ' :resorigcol ' , ',"resorigcol":' + -- Make the regex also match the node type, e.g. \`{QUERY ...\`, to remove it in one pass. + ), '{' , '{ :' + -- Protect node lists, which start with \`({\` or \`((\` from the greedy regex. + -- The extra \`{\` is removed again later. + ), '((' , '{((' + ), '({' , '{({' + -- This regex removes all unused fields to avoid the need to format all of them correctly. + -- This leads to a smaller json result as well. + -- Removal stops at \`,\` for used fields (see above) and \`}\` for the end of the current node. + -- Nesting can't be parsed correctly with a regex, so we stop at \`{\` as well and + -- add an empty key for the followig node. + ), ' :[^}{,]+' , ',"":' , 'g' + -- For performance, the regex also added those empty keys when hitting a \`,\` or \`}\`. + -- Those are removed next. + ), ',"":}' , '}' + ), ',"":,' , ',' + -- This reverses the "node list protection" from above. + ), '{(' , '(' + -- Every key above has been added with a \`,\` so far. The first key in an object doesn't need it. + ), '{,' , '{' + -- pg_node_tree has \`()\` around lists, but JSON uses \`[]\` + ), '(' , '[' + ), ')' , ']' + -- pg_node_tree has \` \` between list items, but JSON uses \`,\` + ), ' ' , ',' + )::json as view_definition + from views +), +target_entries as( + select + view_id, view_schema, view_name, + json_array_elements(view_definition->0->'targetList') as entry + from transform_json +), +results as( + select + view_id, view_schema, view_name, + (entry->>'resno')::int as view_column, + (entry->>'resorigtbl')::oid as resorigtbl, + (entry->>'resorigcol')::int as resorigcol + from target_entries +), +-- CYCLE detection according to PG docs: https://www.postgresql.org/docs/current/queries-with.html#QUERIES-WITH-CYCLE +-- Can be replaced with CYCLE clause once PG v13 is EOL. +recursion(view_id, view_schema, view_name, view_column, resorigtbl, resorigcol, is_cycle, path) as( + select + r.*, + false, + ARRAY[resorigtbl] + from results r + where ${props.schemaFilter ? `view_schema ${props.schemaFilter}` : 'true'} + union all + select + view.view_id, + view.view_schema, + view.view_name, + view.view_column, + tab.resorigtbl, + tab.resorigcol, + tab.resorigtbl = ANY(path), + path || tab.resorigtbl + from recursion view + join results tab on view.resorigtbl=tab.view_id and view.resorigcol=tab.view_column + where not is_cycle +), +repeated_references as( + select + view_id, + view_schema, + view_name, + resorigtbl, + resorigcol, + array_agg(attname) as view_columns + from recursion + join pg_attribute vcol on vcol.attrelid = view_id and vcol.attnum = view_column + group by + view_id, + view_schema, + view_name, + resorigtbl, + resorigcol +) +select + sch.nspname as table_schema, + tbl.relname as table_name, + rep.view_schema, + rep.view_name, + pks_fks.conname as constraint_name, + pks_fks.contype as constraint_type, + jsonb_agg( + jsonb_build_object('table_column', col.attname, 'view_columns', view_columns) order by pks_fks.ord + ) as column_dependencies +from repeated_references rep +join pks_fks using (resorigtbl, resorigcol) +join pg_class tbl on tbl.oid = rep.resorigtbl +join pg_attribute col on col.attrelid = tbl.oid and col.attnum = rep.resorigcol +join pg_namespace sch on sch.oid = tbl.relnamespace +group by sch.nspname, tbl.relname, rep.view_schema, rep.view_name, pks_fks.conname, pks_fks.contype, pks_fks.ncol +-- make sure we only return key for which all columns are referenced in the view - no partial PKs or FKs +having ncol = array_length(array_agg(row(col.attname, view_columns) order by pks_fks.ord), 1) +` diff --git a/src/lib/types.ts b/src/lib/types.ts index 827f4837..26b3bc78 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,15 +1,18 @@ import { Static, Type } from '@sinclair/typebox' +import { DatabaseError } from 'pg-protocol' +import type { Options as PrettierOptions } from 'prettier' +import { PoolConfig as PgPoolConfig } from 'pg' -interface PostgresMetaOk { +export interface FormatterOptions extends PrettierOptions {} + +export interface PostgresMetaOk { data: T error: null } -interface PostgresMetaErr { +export interface PostgresMetaErr { data: null - error: { - message: string - } + error: Partial & { message: string; formattedError?: string } } export type PostgresMetaResult = PostgresMetaOk | PostgresMetaErr @@ -18,7 +21,7 @@ export const postgresColumnSchema = Type.Object({ table_id: Type.Integer(), schema: Type.String(), table: Type.String(), - id: Type.RegEx(/^(\d+)\.(\d+)$/), + id: Type.RegExp(/^(\d+)\.(\d+)$/), ordinal_position: Type.Integer(), name: Type.String(), default_value: Type.Unknown(), @@ -30,13 +33,60 @@ export const postgresColumnSchema = Type.Object({ Type.Literal('BY DEFAULT'), Type.Null(), ]), + is_generated: Type.Boolean(), is_nullable: Type.Boolean(), is_updatable: Type.Boolean(), - enums: Type.Array(Type.Unknown()), + is_unique: Type.Boolean(), + enums: Type.Array(Type.String()), + check: Type.Union([Type.String(), Type.Null()]), comment: Type.Union([Type.String(), Type.Null()]), }) export type PostgresColumn = Static +export const postgresColumnCreateSchema = Type.Object({ + table_id: Type.Integer(), + name: Type.String(), + type: Type.String(), + default_value: Type.Optional(Type.Unknown()), + default_value_format: Type.Optional( + Type.Union([Type.Literal('expression'), Type.Literal('literal')]) + ), + is_identity: Type.Optional(Type.Boolean()), + identity_generation: Type.Optional( + Type.Union([Type.Literal('BY DEFAULT'), Type.Literal('ALWAYS')]) + ), + is_nullable: Type.Optional(Type.Boolean()), + is_primary_key: Type.Optional(Type.Boolean()), + is_unique: Type.Optional(Type.Boolean()), + comment: Type.Optional(Type.String()), + check: Type.Optional(Type.String()), +}) +export type PostgresColumnCreate = Static + +export const postgresColumnUpdateSchema = Type.Object({ + name: Type.Optional(Type.String()), + type: Type.Optional(Type.String()), + drop_default: Type.Optional(Type.Boolean()), + default_value: Type.Optional(Type.Unknown()), + default_value_format: Type.Optional( + Type.Union([Type.Literal('expression'), Type.Literal('literal')]) + ), + is_identity: Type.Optional(Type.Boolean()), + identity_generation: Type.Optional( + Type.Union([Type.Literal('BY DEFAULT'), Type.Literal('ALWAYS')]) + ), + is_nullable: Type.Optional(Type.Boolean()), + is_unique: Type.Optional(Type.Boolean()), + comment: Type.Optional(Type.String()), + check: Type.Optional( + Type.Union( + // Type.Null() must go first: https://github.com/sinclairzx81/typebox/issues/546 + [Type.Null(), Type.String()] + ) + ), +}) +export type PostgresColumnUpdate = Static + // TODO Rethink config.sql export const postgresConfigSchema = Type.Object({ name: Type.Unknown(), @@ -70,36 +120,101 @@ export const postgresExtensionSchema = Type.Object({ }) export type PostgresExtension = Static +export const postgresForeignTableSchema = Type.Object({ + id: Type.Integer(), + schema: Type.String(), + name: Type.String(), + comment: Type.Union([Type.String(), Type.Null()]), + columns: Type.Optional(Type.Array(postgresColumnSchema)), +}) +export type PostgresForeignTable = Static + const postgresFunctionSchema = Type.Object({ id: Type.Integer(), schema: Type.String(), name: Type.String(), language: Type.String(), definition: Type.String(), + complete_statement: Type.String(), + args: Type.Array( + Type.Object({ + mode: Type.Union([ + Type.Literal('in'), + Type.Literal('out'), + Type.Literal('inout'), + Type.Literal('variadic'), + Type.Literal('table'), + ]), + name: Type.String(), + type_id: Type.Number(), + has_default: Type.Boolean(), + }) + ), argument_types: Type.String(), + identity_argument_types: Type.String(), + return_type_id: Type.Integer(), return_type: Type.String(), + return_type_relation_id: Type.Union([Type.Integer(), Type.Null()]), + is_set_returning_function: Type.Boolean(), + prorows: Type.Union([Type.Number(), Type.Null()]), + behavior: Type.Union([ + Type.Literal('IMMUTABLE'), + Type.Literal('STABLE'), + Type.Literal('VOLATILE'), + ]), + security_definer: Type.Boolean(), + config_params: Type.Union([Type.Record(Type.String(), Type.String()), Type.Null()]), }) export type PostgresFunction = Static -export const postgresGrantSchema = Type.Object({ +export const postgresFunctionCreateFunction = Type.Object({ + name: Type.String(), + definition: Type.String(), + args: Type.Optional(Type.Array(Type.String())), + behavior: Type.Optional( + Type.Union([Type.Literal('IMMUTABLE'), Type.Literal('STABLE'), Type.Literal('VOLATILE')]) + ), + config_params: Type.Optional(Type.Record(Type.String(), Type.String())), + schema: Type.Optional(Type.String()), + language: Type.Optional(Type.String()), + return_type: Type.Optional(Type.String()), + security_definer: Type.Optional(Type.Boolean()), +}) +export type PostgresFunctionCreate = Static + +const postgresIndexSchema = Type.Object({ + id: Type.Integer(), table_id: Type.Integer(), - grantor: Type.String(), - grantee: Type.String(), schema: Type.String(), - table_name: Type.String(), - privilege_type: Type.Union([ - Type.Literal('INSERT'), - Type.Literal('SELECT'), - Type.Literal('UPDATE'), - Type.Literal('DELETE'), - Type.Literal('TRUNCATE'), - Type.Literal('REFERENCES'), - Type.Literal('TRIGGER'), - ]), - is_grantable: Type.Boolean(), - with_hierarchy: Type.Boolean(), + number_of_attributes: Type.Integer(), + number_of_key_attributes: Type.Integer(), + is_unique: Type.Boolean(), + is_primary: Type.Boolean(), + is_exclusion: Type.Boolean(), + is_immediate: Type.Boolean(), + is_clustered: Type.Boolean(), + is_valid: Type.Boolean(), + check_xmin: Type.Boolean(), + is_ready: Type.Boolean(), + is_live: Type.Boolean(), + is_replica_identity: Type.Boolean(), + key_attributes: Type.Array(Type.Number()), + collation: Type.Array(Type.Number()), + class: Type.Array(Type.Number()), + options: Type.Array(Type.Number()), + index_predicate: Type.Union([Type.String(), Type.Null()]), + comment: Type.Union([Type.String(), Type.Null()]), + index_definition: Type.String(), + access_method: Type.String(), + index_attributes: Type.Array( + Type.Object({ + attribute_number: Type.Number(), + attribute_name: Type.String(), + data_type: Type.String(), + }) + ), }) -export type PostgresGrant = Static +export type PostgresIndex = Static export const postgresPolicySchema = Type.Object({ id: Type.Integer(), @@ -138,44 +253,13 @@ export const postgresPublicationSchema = Type.Object({ publish_delete: Type.Boolean(), publish_truncate: Type.Boolean(), tables: Type.Union([ - Type.Array( - Type.Object({ - id: Type.Integer(), - name: Type.String(), - schema: Type.String(), - }) - ), + Type.Array(Type.Object({ id: Type.Integer(), name: Type.String(), schema: Type.String() })), Type.Null(), ]), }) export type PostgresPublication = Static -export const postgresTriggerSchema = Type.Object({ - id: Type.Integer(), - enabled_mode: Type.Union([ - Type.Literal('ORIGIN'), - Type.Literal('REPLICA'), - Type.Literal('ALWAYS'), - Type.Literal('DISABLED'), - ]), - name: Type.String(), - table: Type.String(), - schema: Type.String(), - condition: Type.Union([Type.String(), Type.Null()]), - orientation: Type.Union([Type.Literal('ROW'), Type.Literal('STATEMENT')]), - activation: Type.Union([ - Type.Literal('BEFORE'), - Type.Literal('AFTER'), - Type.Literal('INSTEAD OF'), - ]), - events: Type.Array(Type.String()), - function_schema: Type.String(), - function_name: Type.String(), - function_args: Type.Array(Type.String()), -}) -export type PostgresTrigger = Static - -export const postgresRelationshipSchema = Type.Object({ +export const postgresRelationshipOldSchema = Type.Object({ id: Type.Integer(), constraint_name: Type.String(), source_schema: Type.String(), @@ -185,8 +269,25 @@ export const postgresRelationshipSchema = Type.Object({ target_table_name: Type.String(), target_column_name: Type.String(), }) +export const postgresRelationshipSchema = Type.Object({ + foreign_key_name: Type.String(), + schema: Type.String(), + relation: Type.String(), + columns: Type.Array(Type.String()), + is_one_to_one: Type.Boolean(), + referenced_schema: Type.String(), + referenced_relation: Type.String(), + referenced_columns: Type.Array(Type.String()), +}) export type PostgresRelationship = Static +export const PostgresMetaRoleConfigSchema = Type.Object({ + op: Type.Union([Type.Literal('remove'), Type.Literal('add'), Type.Literal('replace')]), + path: Type.String(), + value: Type.Optional(Type.String()), +}) +export type PostgresMetaRoleConfig = Static + export const postgresRoleSchema = Type.Object({ id: Type.Integer(), name: Type.String(), @@ -201,11 +302,45 @@ export const postgresRoleSchema = Type.Object({ connection_limit: Type.Integer(), password: Type.String(), valid_until: Type.Union([Type.String(), Type.Null()]), - config: Type.Union([Type.String(), Type.Null()]), - grants: Type.Array(postgresGrantSchema), + config: Type.Union([Type.String(), Type.Null(), Type.Record(Type.String(), Type.String())]), }) export type PostgresRole = Static +export const postgresRoleCreateSchema = Type.Object({ + name: Type.String(), + password: Type.Optional(Type.String()), + inherit_role: Type.Optional(Type.Boolean()), + can_login: Type.Optional(Type.Boolean()), + is_superuser: Type.Optional(Type.Boolean()), + can_create_db: Type.Optional(Type.Boolean()), + can_create_role: Type.Optional(Type.Boolean()), + is_replication_role: Type.Optional(Type.Boolean()), + can_bypass_rls: Type.Optional(Type.Boolean()), + connection_limit: Type.Optional(Type.Integer()), + member_of: Type.Optional(Type.Array(Type.String())), + members: Type.Optional(Type.Array(Type.String())), + admins: Type.Optional(Type.Array(Type.String())), + valid_until: Type.Optional(Type.String()), + config: Type.Optional(Type.Record(Type.String(), Type.String())), +}) +export type PostgresRoleCreate = Static + +export const postgresRoleUpdateSchema = Type.Object({ + name: Type.Optional(Type.String()), + password: Type.Optional(Type.String()), + inherit_role: Type.Optional(Type.Boolean()), + can_login: Type.Optional(Type.Boolean()), + is_superuser: Type.Optional(Type.Boolean()), + can_create_db: Type.Optional(Type.Boolean()), + can_create_role: Type.Optional(Type.Boolean()), + is_replication_role: Type.Optional(Type.Boolean()), + can_bypass_rls: Type.Optional(Type.Boolean()), + connection_limit: Type.Optional(Type.Integer()), + valid_until: Type.Optional(Type.String()), + config: Type.Optional(Type.Array(PostgresMetaRoleConfigSchema)), +}) +export type PostgresRoleUpdate = Static + export const postgresSchemaSchema = Type.Object({ id: Type.Integer(), name: Type.String(), @@ -242,21 +377,73 @@ export const postgresTableSchema = Type.Object({ live_rows_estimate: Type.Integer(), dead_rows_estimate: Type.Integer(), comment: Type.Union([Type.String(), Type.Null()]), - columns: Type.Array(postgresColumnSchema), - grants: Type.Array(postgresGrantSchema), - policies: Type.Array(postgresPolicySchema), + columns: Type.Optional(Type.Array(postgresColumnSchema)), primary_keys: Type.Array(postgresPrimaryKeySchema), - relationships: Type.Array(postgresRelationshipSchema), + relationships: Type.Array(postgresRelationshipOldSchema), }) export type PostgresTable = Static +export const postgresTableCreateSchema = Type.Object({ + name: Type.String(), + schema: Type.Optional(Type.String()), + comment: Type.Optional(Type.String()), +}) +export type PostgresTableCreate = Static + +export const postgresTableUpdateSchema = Type.Object({ + name: Type.Optional(Type.String()), + schema: Type.Optional(Type.String()), + rls_enabled: Type.Optional(Type.Boolean()), + rls_forced: Type.Optional(Type.Boolean()), + replica_identity: Type.Optional( + Type.Union([ + Type.Literal('DEFAULT'), + Type.Literal('INDEX'), + Type.Literal('FULL'), + Type.Literal('NOTHING'), + ]) + ), + replica_identity_index: Type.Optional(Type.String()), + primary_keys: Type.Optional(Type.Array(Type.Object({ name: Type.String() }))), + comment: Type.Optional(Type.String()), +}) +export type PostgresTableUpdate = Static + +export const postgresTriggerSchema = Type.Object({ + id: Type.Integer(), + table_id: Type.Integer(), + enabled_mode: Type.Union([ + Type.Literal('ORIGIN'), + Type.Literal('REPLICA'), + Type.Literal('ALWAYS'), + Type.Literal('DISABLED'), + ]), + name: Type.String(), + table: Type.String(), + schema: Type.String(), + condition: Type.Union([Type.String(), Type.Null()]), + orientation: Type.Union([Type.Literal('ROW'), Type.Literal('STATEMENT')]), + activation: Type.Union([ + Type.Literal('BEFORE'), + Type.Literal('AFTER'), + Type.Literal('INSTEAD OF'), + ]), + events: Type.Array(Type.String()), + function_schema: Type.String(), + function_name: Type.String(), + function_args: Type.Array(Type.String()), +}) +export type PostgresTrigger = Static + export const postgresTypeSchema = Type.Object({ id: Type.Integer(), name: Type.String(), schema: Type.String(), format: Type.String(), - enums: Type.Array(Type.Unknown()), + enums: Type.Array(Type.String()), + attributes: Type.Array(Type.Object({ name: Type.String(), type_id: Type.Integer() })), comment: Type.Union([Type.String(), Type.Null()]), + type_relation_id: Type.Union([Type.Integer(), Type.Null()]), }) export type PostgresType = Static @@ -267,3 +454,141 @@ export const postgresVersionSchema = Type.Object({ max_connections: Type.Integer(), }) export type PostgresVersion = Static + +export const postgresViewSchema = Type.Object({ + id: Type.Integer(), + schema: Type.String(), + name: Type.String(), + is_updatable: Type.Boolean(), + comment: Type.Union([Type.String(), Type.Null()]), + columns: Type.Optional(Type.Array(postgresColumnSchema)), +}) +export type PostgresView = Static + +export const postgresMaterializedViewSchema = Type.Object({ + id: Type.Integer(), + schema: Type.String(), + name: Type.String(), + is_populated: Type.Boolean(), + comment: Type.Union([Type.String(), Type.Null()]), + columns: Type.Optional(Type.Array(postgresColumnSchema)), +}) +export type PostgresMaterializedView = Static + +export const postgresTablePrivilegesSchema = Type.Object({ + relation_id: Type.Integer(), + schema: Type.String(), + name: Type.String(), + kind: Type.Union([ + Type.Literal('table'), + Type.Literal('view'), + Type.Literal('materialized_view'), + Type.Literal('foreign_table'), + Type.Literal('partitioned_table'), + ]), + privileges: Type.Array( + Type.Object({ + grantor: Type.String(), + grantee: Type.String(), + privilege_type: Type.Union([ + Type.Literal('SELECT'), + Type.Literal('INSERT'), + Type.Literal('UPDATE'), + Type.Literal('DELETE'), + Type.Literal('TRUNCATE'), + Type.Literal('REFERENCES'), + Type.Literal('TRIGGER'), + Type.Literal('MAINTAIN'), + ]), + is_grantable: Type.Boolean(), + }) + ), +}) +export type PostgresTablePrivileges = Static + +export const postgresTablePrivilegesGrantSchema = Type.Object({ + relation_id: Type.Integer(), + grantee: Type.String(), + privilege_type: Type.Union([ + Type.Literal('ALL'), + Type.Literal('SELECT'), + Type.Literal('INSERT'), + Type.Literal('UPDATE'), + Type.Literal('DELETE'), + Type.Literal('TRUNCATE'), + Type.Literal('REFERENCES'), + Type.Literal('TRIGGER'), + Type.Literal('MAINTAIN'), + ]), + is_grantable: Type.Optional(Type.Boolean()), +}) +export type PostgresTablePrivilegesGrant = Static + +export const postgresTablePrivilegesRevokeSchema = Type.Object({ + relation_id: Type.Integer(), + grantee: Type.String(), + privilege_type: Type.Union([ + Type.Literal('ALL'), + Type.Literal('SELECT'), + Type.Literal('INSERT'), + Type.Literal('UPDATE'), + Type.Literal('DELETE'), + Type.Literal('TRUNCATE'), + Type.Literal('REFERENCES'), + Type.Literal('TRIGGER'), + Type.Literal('MAINTAIN'), + ]), +}) +export type PostgresTablePrivilegesRevoke = Static + +export const postgresColumnPrivilegesSchema = Type.Object({ + column_id: Type.RegExp(/^(\d+)\.(\d+)$/), + relation_schema: Type.String(), + relation_name: Type.String(), + column_name: Type.String(), + privileges: Type.Array( + Type.Object({ + grantor: Type.String(), + grantee: Type.String(), + privilege_type: Type.Union([ + Type.Literal('SELECT'), + Type.Literal('INSERT'), + Type.Literal('UPDATE'), + Type.Literal('REFERENCES'), + ]), + is_grantable: Type.Boolean(), + }) + ), +}) +export type PostgresColumnPrivileges = Static + +export const postgresColumnPrivilegesGrantSchema = Type.Object({ + column_id: Type.RegExp(/^(\d+)\.(\d+)$/), + grantee: Type.String(), + privilege_type: Type.Union([ + Type.Literal('ALL'), + Type.Literal('SELECT'), + Type.Literal('INSERT'), + Type.Literal('UPDATE'), + Type.Literal('REFERENCES'), + ]), + is_grantable: Type.Optional(Type.Boolean()), +}) +export type PostgresColumnPrivilegesGrant = Static + +export const postgresColumnPrivilegesRevokeSchema = Type.Object({ + column_id: Type.RegExp(/^(\d+)\.(\d+)$/), + grantee: Type.String(), + privilege_type: Type.Union([ + Type.Literal('ALL'), + Type.Literal('SELECT'), + Type.Literal('INSERT'), + Type.Literal('UPDATE'), + Type.Literal('REFERENCES'), + ]), +}) +export type PostgresColumnPrivilegesRevoke = Static + +export interface PoolConfig extends PgPoolConfig { + maxResultSize?: number +} diff --git a/src/server/admin-app.ts b/src/server/admin-app.ts new file mode 100644 index 00000000..4d6595e7 --- /dev/null +++ b/src/server/admin-app.ts @@ -0,0 +1,14 @@ +import './sentry.js' +import * as Sentry from '@sentry/node' +import { fastify, FastifyInstance, FastifyServerOptions } from 'fastify' +import fastifyMetrics from 'fastify-metrics' + +export function build(opts: FastifyServerOptions = {}): FastifyInstance { + const app = fastify(opts) + Sentry.setupFastifyErrorHandler(app) + app.register(fastifyMetrics.default, { + endpoint: '/metrics', + routeMetrics: { enabled: false }, + }) + return app +} diff --git a/src/server/app.ts b/src/server/app.ts index 9f030027..9df05341 100644 --- a/src/server/app.ts +++ b/src/server/app.ts @@ -1,22 +1,34 @@ -import fastify from 'fastify' -import { PG_META_EXPORT_DOCS, PG_META_PORT } from './constants' -import routes from './routes' -import pkg from '../../package.json' - -const app = fastify({ logger: true, disableRequestLogging: true }) +import './sentry.js' +import * as Sentry from '@sentry/node' +import cors from '@fastify/cors' +import swagger from '@fastify/swagger' +import { fastify, FastifyInstance, FastifyServerOptions } from 'fastify' +import { PG_META_REQ_HEADER, MAX_BODY_LIMIT } from './constants.js' +import routes from './routes/index.js' +import { extractRequestForLogging } from './utils.js' +// Pseudo package declared only for this module +import pkg from '#package.json' with { type: 'json' } + +export const build = (opts: FastifyServerOptions = {}): FastifyInstance => { + const app = fastify({ + disableRequestLogging: true, + requestIdHeader: PG_META_REQ_HEADER, + bodyLimit: MAX_BODY_LIMIT, + ...opts, + }) + Sentry.setupFastifyErrorHandler(app) -app.setErrorHandler((error, request, reply) => { - app.log.error(JSON.stringify({ error, req: request.body })) - reply.code(500).send({ error: error.message }) -}) + app.setErrorHandler((error, request, reply) => { + app.log.error({ error: error.toString(), request: extractRequestForLogging(request) }) + reply.code(500).send({ error: error.message }) + }) -app.setNotFoundHandler((request, reply) => { - app.log.error(JSON.stringify({ error: 'Not found', req: request.body })) - reply.code(404).send({ error: 'Not found' }) -}) + app.setNotFoundHandler((request, reply) => { + app.log.error({ error: 'Not found', request: extractRequestForLogging(request) }) + reply.code(404).send({ error: 'Not found' }) + }) -if (PG_META_EXPORT_DOCS) { - app.register(require('fastify-swagger'), { + app.register(swagger, { openapi: { servers: [], info: { @@ -27,38 +39,22 @@ if (PG_META_EXPORT_DOCS) { }, }) - app.ready(() => { - require('fs').writeFileSync( - 'openapi.json', - JSON.stringify( - // @ts-ignore: app.swagger() is a Fastify decorator, so doesn't show up in the types - app.swagger(), - null, - 2 - ) + '\n' - ) - }) -} else { - app.ready(() => { - app.listen(PG_META_PORT, '0.0.0.0', () => { - app.log.info(`App started on port ${PG_META_PORT}`) - }) - }) -} + app.register(cors) -app.register(require('fastify-cors')) + app.get('/', async (_request, _reply) => { + return { + status: 200, + name: pkg.name, + version: pkg.version, + documentation: 'https://github.com/supabase/postgres-meta', + } + }) -app.get('/', async (_request, _reply) => { - return { - status: 200, - name: pkg.name, - version: pkg.version, - documentation: 'https://github.com/supabase/postgres-meta', - } -}) + app.get('/health', async (_request, _reply) => { + return { date: new Date() } + }) -app.get('/health', async (_request, _reply) => { - return { date: new Date() } -}) + app.register(routes) -app.register(routes) + return app +} diff --git a/src/server/constants.ts b/src/server/constants.ts index 702f7de7..c64b45e6 100644 --- a/src/server/constants.ts +++ b/src/server/constants.ts @@ -1,12 +1,78 @@ +import crypto from 'crypto' +import { PoolConfig } from '../lib/types.js' +import { getSecret } from '../lib/secrets.js' +import { AccessControl } from './templates/swift.js' +import pkg from '#package.json' with { type: 'json' } + +export const PG_META_HOST = process.env.PG_META_HOST || '0.0.0.0' export const PG_META_PORT = Number(process.env.PG_META_PORT || 1337) -export const CRYPTO_KEY = process.env.CRYPTO_KEY || 'SAMPLE_KEY' +export const CRYPTO_KEY = (await getSecret('CRYPTO_KEY')) || 'SAMPLE_KEY' const PG_META_DB_HOST = process.env.PG_META_DB_HOST || 'localhost' const PG_META_DB_NAME = process.env.PG_META_DB_NAME || 'postgres' const PG_META_DB_USER = process.env.PG_META_DB_USER || 'postgres' -const PG_META_DB_PORT = Number(process.env.PG_META_DB_PORT) || 5432 -const PG_META_DB_PASSWORD = process.env.PG_META_DB_PASSWORD || 'postgres' +const PG_META_DB_PORT = process.env.PG_META_DB_PORT || '5432' +const PG_META_DB_PASSWORD = (await getSecret('PG_META_DB_PASSWORD')) || 'postgres' +const PG_META_DB_SSL_MODE = process.env.PG_META_DB_SSL_MODE || 'disable' + +const PG_CONN_TIMEOUT_SECS = Number(process.env.PG_CONN_TIMEOUT_SECS || 15) +const PG_QUERY_TIMEOUT_SECS = Number(process.env.PG_QUERY_TIMEOUT_SECS || 55) + +export let PG_CONNECTION = process.env.PG_META_DB_URL +if (!PG_CONNECTION) { + const pgConn = new URL('postgresql://') + pgConn.hostname = PG_META_DB_HOST + pgConn.port = PG_META_DB_PORT + pgConn.username = PG_META_DB_USER + pgConn.password = PG_META_DB_PASSWORD + pgConn.pathname = encodeURIComponent(PG_META_DB_NAME) + pgConn.searchParams.set('sslmode', PG_META_DB_SSL_MODE) + PG_CONNECTION = `${pgConn}` +} + +export const PG_META_DB_SSL_ROOT_CERT = process.env.PG_META_DB_SSL_ROOT_CERT +if (PG_META_DB_SSL_ROOT_CERT) { + // validate cert + new crypto.X509Certificate(PG_META_DB_SSL_ROOT_CERT) +} + +export const EXPORT_DOCS = process.env.PG_META_EXPORT_DOCS === 'true' +export const GENERATE_TYPES = process.env.PG_META_GENERATE_TYPES +export const GENERATE_TYPES_INCLUDED_SCHEMAS = GENERATE_TYPES + ? (process.env.PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS?.split(',') ?? []) + : [] +export const GENERATE_TYPES_DEFAULT_SCHEMA = + process.env.PG_META_GENERATE_TYPES_DEFAULT_SCHEMA || 'public' +export const GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS = + process.env.PG_META_GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS === 'true' +export const POSTGREST_VERSION = process.env.PG_META_POSTGREST_VERSION +export const GENERATE_TYPES_SWIFT_ACCESS_CONTROL = process.env + .PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL + ? (process.env.PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL as AccessControl) + : 'internal' + +// json/jsonb/text types +export const VALID_UNNAMED_FUNCTION_ARG_TYPES = new Set([114, 3802, 25]) +export const VALID_FUNCTION_ARGS_MODE = new Set(['in', 'inout', 'variadic']) + +export const PG_META_MAX_RESULT_SIZE = process.env.PG_META_MAX_RESULT_SIZE_MB + ? // Node-postgres get a maximum size in bytes make the conversion from the env variable + // from MB to Bytes + parseInt(process.env.PG_META_MAX_RESULT_SIZE_MB, 10) * 1024 * 1024 + : 2 * 1024 * 1024 * 1024 // default to 2GB max query size result + +export const MAX_BODY_LIMIT = process.env.PG_META_MAX_BODY_LIMIT_MB + ? // Fastify server max body size allowed, is in bytes, convert from MB to Bytes + parseInt(process.env.PG_META_MAX_BODY_LIMIT_MB, 10) * 1024 * 1024 + : 3 * 1024 * 1024 -export const PG_CONNECTION = `postgres://${PG_META_DB_USER}:${PG_META_DB_PASSWORD}@${PG_META_DB_HOST}:${PG_META_DB_PORT}/${PG_META_DB_NAME}` +export const DEFAULT_POOL_CONFIG: PoolConfig = { + max: 1, + connectionTimeoutMillis: PG_CONN_TIMEOUT_SECS * 1000, + query_timeout: PG_QUERY_TIMEOUT_SECS * 1000, + ssl: PG_META_DB_SSL_ROOT_CERT ? { ca: PG_META_DB_SSL_ROOT_CERT } : undefined, + application_name: `postgres-meta ${pkg.version}`, + maxResultSize: PG_META_MAX_RESULT_SIZE, +} -export const PG_META_EXPORT_DOCS = process.env.PG_META_EXPORT_DOCS === 'true' || false +export const PG_META_REQ_HEADER = process.env.PG_META_REQ_HEADER || 'request-id' diff --git a/src/server/routes/column-privileges.ts b/src/server/routes/column-privileges.ts new file mode 100644 index 00000000..d1b1f194 --- /dev/null +++ b/src/server/routes/column-privileges.ts @@ -0,0 +1,134 @@ +import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox' +import { Type } from '@sinclair/typebox' +import { PostgresMeta } from '../../lib/index.js' +import { + postgresColumnPrivilegesGrantSchema, + postgresColumnPrivilegesRevokeSchema, + postgresColumnPrivilegesSchema, +} from '../../lib/types.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging, translateErrorToResponseCode } from '../utils.js' + +const route: FastifyPluginAsyncTypebox = async (fastify) => { + fastify.get( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + querystring: Type.Object({ + include_system_schemas: Type.Optional(Type.Boolean()), + // Note: this only supports comma separated values (e.g., "...?included_schemas=public,core") + included_schemas: Type.Optional(Type.String()), + excluded_schemas: Type.Optional(Type.String()), + limit: Type.Optional(Type.Integer()), + offset: Type.Optional(Type.Integer()), + }), + response: { + 200: Type.Array(postgresColumnPrivilegesSchema), + 500: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const includeSystemSchemas = request.query.include_system_schemas + const includedSchemas = request.query.included_schemas?.split(',') + const excludedSchemas = request.query.excluded_schemas?.split(',') + const limit = request.query.limit + const offset = request.query.offset + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.columnPrivileges.list({ + includeSystemSchemas, + includedSchemas, + excludedSchemas, + limit, + offset, + }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(translateErrorToResponseCode(error, 500)) + return { error: error.message } + } + + return data + } + ) + + fastify.post( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + body: Type.Array(postgresColumnPrivilegesGrantSchema), + response: { + 200: Type.Array(postgresColumnPrivilegesSchema), + 400: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.columnPrivileges.grant(request.body) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + return { error: error.message } + } + + return data + } + ) + + fastify.delete( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + body: Type.Array(postgresColumnPrivilegesRevokeSchema), + response: { + 200: Type.Array(postgresColumnPrivilegesSchema), + 400: Type.Object({ + error: Type.String(), + }), + 404: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.columnPrivileges.revoke(request.body) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + if (error.message.startsWith('Cannot find')) reply.code(404) + return { error: error.message } + } + + return data + } + ) +} +export default route diff --git a/src/server/routes/columns.ts b/src/server/routes/columns.ts index e175e31b..21e12364 100644 --- a/src/server/routes/columns.ts +++ b/src/server/routes/columns.ts @@ -1,113 +1,247 @@ -import { FastifyInstance } from 'fastify' -import { PostgresMeta } from '../../lib' - -export default async (fastify: FastifyInstance) => { - fastify.get<{ - Headers: { pg: string } - Querystring: { - include_system_schemas?: string - } - }>('/', async (request, reply) => { - const connectionString = request.headers.pg - const includeSystemSchemas = request.query.include_system_schemas === 'true' - - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.columns.list({ - includeSystemSchemas, - }) - await pgMeta.end() - if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(500) - return { error: error.message } - } +import { PostgresMeta } from '../../lib/index.js' +import { createConnectionConfig, extractRequestForLogging } from '../utils.js' +import { Type } from '@sinclair/typebox' +import { + postgresColumnCreateSchema, + postgresColumnSchema, + postgresColumnUpdateSchema, +} from '../../lib/types.js' +import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox' - return data - }) +const route: FastifyPluginAsyncTypebox = async (fastify) => { + fastify.get( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + querystring: Type.Object({ + include_system_schemas: Type.Optional(Type.Boolean()), + included_schemas: Type.Optional(Type.String()), + excluded_schemas: Type.Optional(Type.String()), + limit: Type.Optional(Type.Integer()), + offset: Type.Optional(Type.Integer()), + }), + response: { + 200: Type.Array(postgresColumnSchema), + 500: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const includeSystemSchemas = request.query.include_system_schemas + const includedSchemas = request.query.included_schemas?.split(',') + const excludedSchemas = request.query.excluded_schemas?.split(',') + const limit = request.query.limit + const offset = request.query.offset - fastify.get<{ - Headers: { pg: string } - Params: { - id: string - } - }>('/:id(\\d+\\.\\d+)', async (request, reply) => { - const connectionString = request.headers.pg - - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.columns.retrieve({ id: request.params.id }) - await pgMeta.end() - if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(400) - if (error.message.startsWith('Cannot find')) reply.code(404) - return { error: error.message } - } + const config = createConnectionConfig(request) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.columns.list({ + includeSystemSchemas, + includedSchemas, + excludedSchemas, + limit, + offset, + }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(500) + return { error: error.message } + } - return data - }) - - fastify.post<{ - Headers: { pg: string } - Body: any - }>('/', async (request, reply) => { - const connectionString = request.headers.pg - - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.columns.create(request.body) - await pgMeta.end() - if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(400) - if (error.message.startsWith('Cannot find')) reply.code(404) - return { error: error.message } + return data } + ) - return data - }) + fastify.get( + '/:tableId(^\\d+):ordinalPosition', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + params: Type.Object({ + tableId: Type.String(), + ordinalPosition: Type.String(), + }), + querystring: Type.Object({ + include_system_schemas: Type.Optional(Type.Boolean()), + limit: Type.Optional(Type.Integer()), + offset: Type.Optional(Type.Integer()), + }), + response: { + 200: postgresColumnSchema, + 400: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + if (request.params.ordinalPosition === '') { + const { + query: { limit, offset }, + params: { tableId }, + } = request + const includeSystemSchemas = request.query.include_system_schemas - fastify.patch<{ - Headers: { pg: string } - Params: { - id: string - } - Body: any - }>('/:id(\\d+\\.\\d+)', async (request, reply) => { - const connectionString = request.headers.pg - - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.columns.update(request.params.id, request.body) - await pgMeta.end() - if (error) { - request.log.error({ error, req: request.body }) - reply.code(400) - if (error.message.startsWith('Cannot find')) reply.code(404) - return { error: error.message } - } + const config = createConnectionConfig(request) + const pgMeta: PostgresMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.columns.list({ + tableId: Number(tableId), + includeSystemSchemas, + limit: Number(limit), + offset: Number(offset), + }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(500) + return { error: error.message } + } - return data - }) + return data[0] + } else if (/^\.\d+$/.test(request.params.ordinalPosition)) { + const { + params: { tableId, ordinalPosition: ordinalPositionWithDot }, + } = request + const ordinalPosition = ordinalPositionWithDot.slice(1) - fastify.delete<{ - Headers: { pg: string } - Params: { - id: string + const config = createConnectionConfig(request) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.columns.retrieve({ + id: `${tableId}.${ordinalPosition}`, + }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + if (error.message.startsWith('Cannot find')) reply.code(404) + return { error: error.message } + } + + return data + } else { + return reply.callNotFound() + } } - Querystring: { - cascade?: string + ) + + fastify.post( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + body: postgresColumnCreateSchema, + response: { + 200: postgresColumnSchema, + 400: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.columns.create(request.body) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + if (error.message.startsWith('Cannot find')) reply.code(404) + return { error: error.message } + } + + return data } - }>('/:id(\\d+\\.\\d+)', async (request, reply) => { - const connectionString = request.headers.pg - - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.columns.remove(request.params.id) - await pgMeta.end() - if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(400) - if (error.message.startsWith('Cannot find')) reply.code(404) - return { error: error.message } + ) + + fastify.patch( + '/:id(\\d+\\.\\d+)', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + params: Type.Object({ + id: Type.String(), + }), + body: postgresColumnUpdateSchema, + response: { + 200: postgresColumnSchema, + 400: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.columns.update(request.params.id, request.body) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + if (error.message.startsWith('Cannot find')) reply.code(404) + return { error: error.message } + } + + return data } + ) + + fastify.delete( + '/:id(\\d+\\.\\d+)', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + params: Type.Object({ + id: Type.String(), + }), + querystring: Type.Object({ + cascade: Type.Optional(Type.String()), + }), + response: { + 200: postgresColumnSchema, + 400: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const cascade = request.query.cascade === 'true' - return data - }) + const config = createConnectionConfig(request) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.columns.remove(request.params.id, { cascade }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + if (error.message.startsWith('Cannot find')) reply.code(404) + return { error: error.message } + } + + return data + } + ) } + +export default route diff --git a/src/server/routes/config.ts b/src/server/routes/config.ts index f50d8d9c..a2faf9db 100644 --- a/src/server/routes/config.ts +++ b/src/server/routes/config.ts @@ -1,17 +1,25 @@ import { FastifyInstance } from 'fastify' -import { PostgresMeta } from '../../lib' +import { PostgresMeta } from '../../lib/index.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } + Querystring: { + limit?: number + offset?: number + } }>('/', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) + const limit = request.query.limit + const offset = request.query.offset - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.config.list() + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.config.list({ limit, offset }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(500) return { error: error.message } } @@ -20,15 +28,15 @@ export default async (fastify: FastifyInstance) => { }) fastify.get<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } }>('/version', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.version.retrieve() await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(500) return { error: error.message } } diff --git a/src/server/routes/extensions.ts b/src/server/routes/extensions.ts index 1b62d024..b202c251 100644 --- a/src/server/routes/extensions.ts +++ b/src/server/routes/extensions.ts @@ -1,17 +1,25 @@ import { FastifyInstance } from 'fastify' -import { PostgresMeta } from '../../lib' +import { PostgresMeta } from '../../lib/index.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } + Querystring: { + limit?: number + offset?: number + } }>('/', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) + const limit = request.query.limit + const offset = request.query.offset - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.extensions.list() + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.extensions.list({ limit, offset }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(500) return { error: error.message } } @@ -20,18 +28,18 @@ export default async (fastify: FastifyInstance) => { }) fastify.get<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { name: string } }>('/:name', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.extensions.retrieve({ name: request.params.name }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(404) return { error: error.message } } @@ -40,16 +48,16 @@ export default async (fastify: FastifyInstance) => { }) fastify.post<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Body: any }>('/', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.extensions.create(request.body) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.extensions.create(request.body as any) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) return { error: error.message } } @@ -58,19 +66,19 @@ export default async (fastify: FastifyInstance) => { }) fastify.patch<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { name: string } Body: any }>('/:name', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.extensions.update(request.params.name, request.body) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.extensions.update(request.params.name, request.body as any) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) if (error.message.startsWith('Cannot find')) reply.code(404) return { error: error.message } @@ -80,7 +88,7 @@ export default async (fastify: FastifyInstance) => { }) fastify.delete<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { name: string } @@ -88,14 +96,14 @@ export default async (fastify: FastifyInstance) => { cascade?: string } }>('/:name', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const cascade = request.query.cascade === 'true' - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.extensions.remove(request.params.name, { cascade }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) if (error.message.startsWith('Cannot find')) reply.code(404) return { error: error.message } diff --git a/src/server/routes/foreign-tables.ts b/src/server/routes/foreign-tables.ts new file mode 100644 index 00000000..4de924f6 --- /dev/null +++ b/src/server/routes/foreign-tables.ts @@ -0,0 +1,85 @@ +import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox' +import { Type } from '@sinclair/typebox' +import { PostgresMeta } from '../../lib/index.js' +import { postgresForeignTableSchema } from '../../lib/types.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging } from '../utils.js' + +const route: FastifyPluginAsyncTypebox = async (fastify) => { + fastify.get( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + querystring: Type.Object({ + limit: Type.Optional(Type.Integer()), + offset: Type.Optional(Type.Integer()), + include_columns: Type.Optional(Type.Boolean()), + }), + response: { + 200: Type.Array(postgresForeignTableSchema), + 500: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const limit = request.query.limit + const offset = request.query.offset + const includeColumns = request.query.include_columns + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.foreignTables.list({ limit, offset, includeColumns }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(500) + return { error: error.message } + } + + return data + } + ) + + fastify.get( + '/:id(\\d+)', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + params: Type.Object({ + id: Type.Integer(), + }), + response: { + 200: postgresForeignTableSchema, + 404: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const id = request.params.id + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.foreignTables.retrieve({ id }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(404) + return { error: error.message } + } + + return data + } + ) +} +export default route diff --git a/src/server/routes/functions.ts b/src/server/routes/functions.ts index 84c2d702..51fd5737 100644 --- a/src/server/routes/functions.ts +++ b/src/server/routes/functions.ts @@ -1,21 +1,38 @@ import { FastifyInstance } from 'fastify' -import { PostgresMeta } from '../../lib' +import { PostgresMeta } from '../../lib/index.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Querystring: { include_system_schemas?: string + // Note: this only supports comma separated values (e.g., ".../functions?included_schemas=public,core") + included_schemas?: string + excluded_schemas?: string + limit?: number + offset?: number } }>('/', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const includeSystemSchemas = request.query.include_system_schemas === 'true' + const includedSchemas = request.query.included_schemas?.split(',') + const excludedSchemas = request.query.excluded_schemas?.split(',') + const limit = request.query.limit + const offset = request.query.offset - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.functions.list({ includeSystemSchemas }) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.functions.list({ + includeSystemSchemas, + includedSchemas, + excludedSchemas, + limit, + offset, + }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(500) return { error: error.message } } @@ -24,19 +41,19 @@ export default async (fastify: FastifyInstance) => { }) fastify.get<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.functions.retrieve({ id }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(404) return { error: error.message } } @@ -45,16 +62,16 @@ export default async (fastify: FastifyInstance) => { }) fastify.post<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Body: any }>('/', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.functions.create(request.body) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.functions.create(request.body as any) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) return { error: error.message } } @@ -62,20 +79,20 @@ export default async (fastify: FastifyInstance) => { }) fastify.patch<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { id: string } Body: any }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.functions.update(id, request.body) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.functions.update(id, request.body as any) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) if (error.message.startsWith('Cannot find')) reply.code(404) return { error: error.message } @@ -84,19 +101,19 @@ export default async (fastify: FastifyInstance) => { }) fastify.delete<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.functions.remove(id) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) if (error.message.startsWith('Cannot find')) reply.code(404) return { error: error.message } diff --git a/src/server/routes/generators/go.ts b/src/server/routes/generators/go.ts new file mode 100644 index 00000000..fa85fa46 --- /dev/null +++ b/src/server/routes/generators/go.ts @@ -0,0 +1,34 @@ +import type { FastifyInstance } from 'fastify' +import { PostgresMeta } from '../../../lib/index.js' +import { createConnectionConfig, extractRequestForLogging } from '../../utils.js' +import { apply as applyGoTemplate } from '../../templates/go.js' +import { getGeneratorMetadata } from '../../../lib/generators.js' + +export default async (fastify: FastifyInstance) => { + fastify.get<{ + Headers: { pg: string; 'x-pg-application-name'?: string } + Querystring: { + excluded_schemas?: string + included_schemas?: string + } + }>('/', async (request, reply) => { + const config = createConnectionConfig(request) + const excludedSchemas = + request.query.excluded_schemas?.split(',').map((schema) => schema.trim()) ?? [] + const includedSchemas = + request.query.included_schemas?.split(',').map((schema) => schema.trim()) ?? [] + + const pgMeta: PostgresMeta = new PostgresMeta(config) + const { data: generatorMeta, error: generatorMetaError } = await getGeneratorMetadata(pgMeta, { + includedSchemas, + excludedSchemas, + }) + if (generatorMetaError) { + request.log.error({ error: generatorMetaError, request: extractRequestForLogging(request) }) + reply.code(500) + return { error: generatorMetaError.message } + } + + return applyGoTemplate(generatorMeta) + }) +} diff --git a/src/server/routes/generators/python.ts b/src/server/routes/generators/python.ts new file mode 100644 index 00000000..706d9dd4 --- /dev/null +++ b/src/server/routes/generators/python.ts @@ -0,0 +1,33 @@ +import type { FastifyInstance } from 'fastify' +import { PostgresMeta } from '../../../lib/index.js' +import { createConnectionConfig, extractRequestForLogging } from '../../utils.js' +import { apply as applyPyTemplate } from '../../templates/python.js' +import { getGeneratorMetadata } from '../../../lib/generators.js' + +export default async (fastify: FastifyInstance) => { + fastify.get<{ + Headers: { pg: string; 'x-pg-application-name'?: string } + Querystring: { + excluded_schemas?: string + included_schemas?: string + } + }>('/', async (request, reply) => { + const config = createConnectionConfig(request) + const excludedSchemas = + request.query.excluded_schemas?.split(',').map((schema) => schema.trim()) ?? [] + const includedSchemas = + request.query.included_schemas?.split(',').map((schema) => schema.trim()) ?? [] + const pgMeta: PostgresMeta = new PostgresMeta(config) + const { data: generatorMeta, error: generatorMetaError } = await getGeneratorMetadata(pgMeta, { + includedSchemas, + excludedSchemas, + }) + if (generatorMetaError) { + request.log.error({ error: generatorMetaError, request: extractRequestForLogging(request) }) + reply.code(500) + return { error: generatorMetaError.message } + } + + return applyPyTemplate(generatorMeta) + }) +} diff --git a/src/server/routes/generators/swift.ts b/src/server/routes/generators/swift.ts new file mode 100644 index 00000000..e02839fb --- /dev/null +++ b/src/server/routes/generators/swift.ts @@ -0,0 +1,39 @@ +import type { FastifyInstance } from 'fastify' +import { PostgresMeta } from '../../../lib/index.js' +import { createConnectionConfig, extractRequestForLogging } from '../../utils.js' +import { apply as applySwiftTemplate, AccessControl } from '../../templates/swift.js' +import { getGeneratorMetadata } from '../../../lib/generators.js' + +export default async (fastify: FastifyInstance) => { + fastify.get<{ + Headers: { pg: string; 'x-pg-application-name'?: string } + Querystring: { + excluded_schemas?: string + included_schemas?: string + access_control?: AccessControl + } + }>('/', async (request, reply) => { + const config = createConnectionConfig(request) + const excludedSchemas = + request.query.excluded_schemas?.split(',').map((schema) => schema.trim()) ?? [] + const includedSchemas = + request.query.included_schemas?.split(',').map((schema) => schema.trim()) ?? [] + const accessControl = request.query.access_control ?? 'internal' + + const pgMeta: PostgresMeta = new PostgresMeta(config) + const { data: generatorMeta, error: generatorMetaError } = await getGeneratorMetadata(pgMeta, { + includedSchemas, + excludedSchemas, + }) + if (generatorMetaError) { + request.log.error({ error: generatorMetaError, request: extractRequestForLogging(request) }) + reply.code(500) + return { error: generatorMetaError.message } + } + + return applySwiftTemplate({ + ...generatorMeta, + accessControl, + }) + }) +} diff --git a/src/server/routes/generators/typescript.ts b/src/server/routes/generators/typescript.ts new file mode 100644 index 00000000..259cd141 --- /dev/null +++ b/src/server/routes/generators/typescript.ts @@ -0,0 +1,42 @@ +import type { FastifyInstance } from 'fastify' +import { PostgresMeta } from '../../../lib/index.js' +import { createConnectionConfig, extractRequestForLogging } from '../../utils.js' +import { apply as applyTypescriptTemplate } from '../../templates/typescript.js' +import { getGeneratorMetadata } from '../../../lib/generators.js' + +export default async (fastify: FastifyInstance) => { + fastify.get<{ + Headers: { pg: string; 'x-pg-application-name'?: string } + Querystring: { + excluded_schemas?: string + included_schemas?: string + detect_one_to_one_relationships?: string + postgrest_version?: string + } + }>('/', async (request, reply) => { + const config = createConnectionConfig(request) + const excludedSchemas = + request.query.excluded_schemas?.split(',').map((schema) => schema.trim()) ?? [] + const includedSchemas = + request.query.included_schemas?.split(',').map((schema) => schema.trim()) ?? [] + const detectOneToOneRelationships = request.query.detect_one_to_one_relationships === 'true' + const postgrestVersion = request.query.postgrest_version + + const pgMeta: PostgresMeta = new PostgresMeta(config) + const { data: generatorMeta, error: generatorMetaError } = await getGeneratorMetadata(pgMeta, { + includedSchemas, + excludedSchemas, + }) + if (generatorMetaError) { + request.log.error({ error: generatorMetaError, request: extractRequestForLogging(request) }) + reply.code(500) + return { error: generatorMetaError.message } + } + + return applyTypescriptTemplate({ + ...generatorMeta, + detectOneToOneRelationships, + postgrestVersion, + }) + }) +} diff --git a/src/server/routes/index.ts b/src/server/routes/index.ts index b636ac9e..46ffba0f 100644 --- a/src/server/routes/index.ts +++ b/src/server/routes/index.ts @@ -1,32 +1,87 @@ import CryptoJS from 'crypto-js' import { FastifyInstance } from 'fastify' -import { PG_CONNECTION, CRYPTO_KEY } from '../constants' +import ColumnPrivilegesRoute from './column-privileges.js' +import ColumnRoute from './columns.js' +import ConfigRoute from './config.js' +import ExtensionsRoute from './extensions.js' +import ForeignTablesRoute from './foreign-tables.js' +import FunctionsRoute from './functions.js' +import IndexesRoute from './indexes.js' +import MaterializedViewsRoute from './materialized-views.js' +import PoliciesRoute from './policies.js' +import PublicationsRoute from './publications.js' +import QueryRoute from './query.js' +import SchemasRoute from './schemas.js' +import RolesRoute from './roles.js' +import TablePrivilegesRoute from './table-privileges.js' +import TablesRoute from './tables.js' +import TriggersRoute from './triggers.js' +import TypesRoute from './types.js' +import ViewsRoute from './views.js' +import TypeScriptTypeGenRoute from './generators/typescript.js' +import GoTypeGenRoute from './generators/go.js' +import SwiftTypeGenRoute from './generators/swift.js' +import PythonTypeGenRoute from './generators/python.js' +import { PG_CONNECTION, CRYPTO_KEY } from '../constants.js' export default async (fastify: FastifyInstance) => { // Adds a "pg" object to the request if it doesn't exist fastify.addHook('onRequest', (request, _reply, done) => { - // Node converts headers to lowercase - const encryptedHeader = request.headers['x-connection-encrypted']?.toString() - if (encryptedHeader) { - request.headers.pg = CryptoJS.AES.decrypt(encryptedHeader, CRYPTO_KEY).toString( - CryptoJS.enc.Utf8 - ) - } else { - request.headers.pg = PG_CONNECTION + try { + // Node converts headers to lowercase + const encryptedHeader = request.headers['x-connection-encrypted']?.toString() + if (encryptedHeader) { + try { + request.headers.pg = CryptoJS.AES.decrypt(encryptedHeader, CRYPTO_KEY) + .toString(CryptoJS.enc.Utf8) + .trim() + } catch (e: any) { + request.log.warn({ + message: 'failed to parse encrypted connstring', + error: e.toString(), + }) + throw new Error('failed to process upstream connection details') + } + } else { + request.headers.pg = PG_CONNECTION + } + if (!request.headers.pg) { + request.log.error({ message: 'failed to get connection string' }) + throw new Error('failed to get upstream connection details') + } + // Ensure the resulting connection string is a valid URL + try { + new URL(request.headers.pg) + } catch (error) { + request.log.error({ message: 'pg connection string is invalid url' }) + throw new Error('failed to process upstream connection details') + } + return done() + } catch (err) { + return done(err as Error) } - done() }) - fastify.register(require('./config'), { prefix: '/config' }) - fastify.register(require('./columns'), { prefix: '/columns' }) - fastify.register(require('./extensions'), { prefix: '/extensions' }) - fastify.register(require('./functions'), { prefix: '/functions' }) - fastify.register(require('./policies'), { prefix: '/policies' }) - fastify.register(require('./publications'), { prefix: '/publications' }) - fastify.register(require('./query'), { prefix: '/query' }) - fastify.register(require('./schemas'), { prefix: '/schemas' }) - fastify.register(require('./tables'), { prefix: '/tables' }) - fastify.register(require('./triggers'), { prefix: '/triggers' }) - fastify.register(require('./types'), { prefix: '/types' }) - fastify.register(require('./roles'), { prefix: '/roles' }) + fastify.register(ColumnPrivilegesRoute, { prefix: '/column-privileges' }) + fastify.register(ColumnRoute, { prefix: '/columns' }) + fastify.register(ConfigRoute, { prefix: '/config' }) + fastify.register(ExtensionsRoute, { prefix: '/extensions' }) + fastify.register(ForeignTablesRoute, { prefix: '/foreign-tables' }) + fastify.register(FunctionsRoute, { prefix: '/functions' }) + fastify.register(IndexesRoute, { prefix: '/indexes' }) + fastify.register(MaterializedViewsRoute, { prefix: '/materialized-views' }) + fastify.register(PoliciesRoute, { prefix: '/policies' }) + fastify.register(PublicationsRoute, { prefix: '/publications' }) + fastify.register(QueryRoute, { prefix: '/query' }) + fastify.register(SchemasRoute, { prefix: '/schemas' }) + fastify.register(RolesRoute, { prefix: '/roles' }) + fastify.register(TablePrivilegesRoute, { prefix: '/table-privileges' }) + fastify.register(TablesRoute, { prefix: '/tables' }) + fastify.register(TriggersRoute, { prefix: '/triggers' }) + fastify.register(TypesRoute, { prefix: '/types' }) + fastify.register(ViewsRoute, { prefix: '/views' }) + fastify.register(TypeScriptTypeGenRoute, { prefix: '/generators/typescript' }) + fastify.register(GoTypeGenRoute, { prefix: '/generators/go' }) + fastify.register(SwiftTypeGenRoute, { prefix: '/generators/swift' }) + fastify.register(PythonTypeGenRoute, { prefix: '/generators/python' }) } diff --git a/src/server/routes/indexes.ts b/src/server/routes/indexes.ts new file mode 100644 index 00000000..b024ea09 --- /dev/null +++ b/src/server/routes/indexes.ts @@ -0,0 +1,62 @@ +import { FastifyInstance } from 'fastify' +import { PostgresMeta } from '../../lib/index.js' +import { createConnectionConfig, extractRequestForLogging } from '../utils.js' + +export default async (fastify: FastifyInstance) => { + fastify.get<{ + Headers: { pg: string; 'x-pg-application-name'?: string } + Querystring: { + include_system_schemas?: string + // Note: this only supports comma separated values (e.g., ".../functions?included_schemas=public,core") + included_schemas?: string + excluded_schemas?: string + limit?: number + offset?: number + } + }>('/', async (request, reply) => { + const config = createConnectionConfig(request) + const includeSystemSchemas = request.query.include_system_schemas === 'true' + const includedSchemas = request.query.included_schemas?.split(',') + const excludedSchemas = request.query.excluded_schemas?.split(',') + const limit = request.query.limit + const offset = request.query.offset + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.indexes.list({ + includeSystemSchemas, + includedSchemas, + excludedSchemas, + limit, + offset, + }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(500) + return { error: error.message } + } + + return data + }) + + fastify.get<{ + Headers: { pg: string; 'x-pg-application-name'?: string } + Params: { + id: string + } + }>('/:id(\\d+)', async (request, reply) => { + const config = createConnectionConfig(request) + const id = Number(request.params.id) + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.indexes.retrieve({ id }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(404) + return { error: error.message } + } + + return data + }) +} diff --git a/src/server/routes/materialized-views.ts b/src/server/routes/materialized-views.ts new file mode 100644 index 00000000..9a5a0b4f --- /dev/null +++ b/src/server/routes/materialized-views.ts @@ -0,0 +1,95 @@ +import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox' +import { Type } from '@sinclair/typebox' +import { PostgresMeta } from '../../lib/index.js' +import { postgresMaterializedViewSchema } from '../../lib/types.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging } from '../utils.js' + +const route: FastifyPluginAsyncTypebox = async (fastify) => { + fastify.get( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + querystring: Type.Object({ + included_schemas: Type.Optional(Type.String()), + excluded_schemas: Type.Optional(Type.String()), + limit: Type.Optional(Type.Integer()), + offset: Type.Optional(Type.Integer()), + include_columns: Type.Optional(Type.Boolean()), + }), + response: { + 200: Type.Array(postgresMaterializedViewSchema), + 500: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const includedSchemas = request.query.included_schemas?.split(',') + const excludedSchemas = request.query.excluded_schemas?.split(',') + const limit = request.query.limit + const offset = request.query.offset + const includeColumns = request.query.include_columns + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.materializedViews.list({ + includedSchemas, + excludedSchemas, + limit, + offset, + includeColumns, + }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(500) + return { error: error.message } + } + + return data + } + ) + + fastify.get( + '/:id(\\d+)', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + params: Type.Object({ + id: Type.Integer(), + }), + response: { + 200: postgresMaterializedViewSchema, + 404: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const id = request.params.id + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.materializedViews.retrieve({ id }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(404) + return { error: error.message } + } + + return data + } + ) +} +export default route diff --git a/src/server/routes/policies.ts b/src/server/routes/policies.ts index 2f11f868..4e951ad3 100644 --- a/src/server/routes/policies.ts +++ b/src/server/routes/policies.ts @@ -1,21 +1,38 @@ import { FastifyInstance } from 'fastify' -import { PostgresMeta } from '../../lib' +import { PostgresMeta } from '../../lib/index.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Querystring: { include_system_schemas?: string + // Note: this only supports comma separated values (e.g., ".../policies?included_schemas=public,core") + included_schemas?: string + excluded_schemas?: string + limit?: number + offset?: number } }>('/', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const includeSystemSchemas = request.query.include_system_schemas === 'true' + const includedSchemas = request.query.included_schemas?.split(',') + const excludedSchemas = request.query.excluded_schemas?.split(',') + const limit = request.query.limit + const offset = request.query.offset - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.policies.list({ includeSystemSchemas }) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.policies.list({ + includeSystemSchemas, + includedSchemas, + excludedSchemas, + limit, + offset, + }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(500) return { error: error.message } } @@ -24,19 +41,19 @@ export default async (fastify: FastifyInstance) => { }) fastify.get<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.policies.retrieve({ id }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(404) return { error: error.message } } @@ -45,16 +62,16 @@ export default async (fastify: FastifyInstance) => { }) fastify.post<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Body: any }>('/', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.policies.create(request.body) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.policies.create(request.body as any) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) return { error: error.message } } @@ -63,20 +80,20 @@ export default async (fastify: FastifyInstance) => { }) fastify.patch<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { id: string } Body: any }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.policies.update(id, request.body) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.policies.update(id, request.body as any) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) if (error.message.startsWith('Cannot find')) reply.code(404) return { error: error.message } @@ -86,19 +103,19 @@ export default async (fastify: FastifyInstance) => { }) fastify.delete<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.policies.remove(id) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) if (error.message.startsWith('Cannot find')) reply.code(404) return { error: error.message } diff --git a/src/server/routes/publications.ts b/src/server/routes/publications.ts index 5dd6506b..68cf45b2 100644 --- a/src/server/routes/publications.ts +++ b/src/server/routes/publications.ts @@ -1,17 +1,25 @@ import { FastifyInstance } from 'fastify' -import { PostgresMeta } from '../../lib' +import { PostgresMeta } from '../../lib/index.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } + Querystring: { + limit?: number + offset?: number + } }>('/', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) + const limit = request.query.limit + const offset = request.query.offset - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.publications.list() + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.publications.list({ limit, offset }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(500) return { error: error.message } } @@ -20,19 +28,19 @@ export default async (fastify: FastifyInstance) => { }) fastify.get<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.publications.retrieve({ id }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(404) return { error: error.message } } @@ -41,16 +49,16 @@ export default async (fastify: FastifyInstance) => { }) fastify.post<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Body: any }>('/', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.publications.create(request.body) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.publications.create(request.body as any) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) return { error: error.message } } @@ -59,20 +67,20 @@ export default async (fastify: FastifyInstance) => { }) fastify.patch<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { id: string } Body: any }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.publications.update(id, request.body) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.publications.update(id, request.body as any) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) if (error.message.startsWith('Cannot find')) reply.code(404) return { error: error.message } @@ -82,19 +90,19 @@ export default async (fastify: FastifyInstance) => { }) fastify.delete<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.publications.remove(id) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) if (error.message.startsWith('Cannot find')) reply.code(404) return { error: error.message } diff --git a/src/server/routes/query.ts b/src/server/routes/query.ts index 73a4fe02..c6bea0c6 100644 --- a/src/server/routes/query.ts +++ b/src/server/routes/query.ts @@ -1,24 +1,87 @@ -import { FastifyInstance } from 'fastify' -import { PostgresMeta } from '../../lib' +import { FastifyInstance, FastifyRequest } from 'fastify' +import { PostgresMeta } from '../../lib/index.js' +import * as Parser from '../../lib/Parser.js' +import { + createConnectionConfig, + extractRequestForLogging, + translateErrorToResponseCode, +} from '../utils.js' + +const errorOnEmptyQuery = (request: FastifyRequest) => { + if (!(request.body as any).query) { + throw new Error('query not found') + } +} export default async (fastify: FastifyInstance) => { fastify.post<{ - Headers: { pg: string } - Body: { - query: string - } + Headers: { pg: string; 'x-pg-application-name'?: string } + Body: { query: string; parameters?: unknown[] } + Querystring: { statementTimeoutSecs?: number } }>('/', async (request, reply) => { - const connectionString = request.headers.pg - - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.query(request.body.query) + const statementTimeoutSecs = request.query.statementTimeoutSecs + errorOnEmptyQuery(request) + const config = createConnectionConfig(request) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.query(request.body.query, { + trackQueryInSentry: true, + statementQueryTimeout: statementTimeoutSecs, + parameters: request.body.parameters, + }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(400) - return { error: error.message } + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(translateErrorToResponseCode(error)) + return { error: error.formattedError ?? error.message, ...error } } return data || [] }) + + fastify.post<{ + Headers: { pg: string; 'x-pg-application-name'?: string } + Body: { query: string } + }>('/format', async (request, reply) => { + errorOnEmptyQuery(request) + const { data, error } = await Parser.Format(request.body.query) + + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(translateErrorToResponseCode(error)) + return { error: error.message } + } + + return data + }) + + fastify.post<{ + Headers: { pg: string; 'x-pg-application-name'?: string } + Body: { query: string } + }>('/parse', async (request, reply) => { + errorOnEmptyQuery(request) + const { data, error } = Parser.Parse(request.body.query) + + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(translateErrorToResponseCode(error)) + return { error: error.message } + } + + return data + }) + + fastify.post<{ + Headers: { pg: string; 'x-pg-application-name'?: string } + Body: { ast: object } + }>('/deparse', async (request, reply) => { + const { data, error } = await Parser.Deparse(request.body.ast) + + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(translateErrorToResponseCode(error)) + return { error: error.message } + } + + return data + }) } diff --git a/src/server/routes/roles.ts b/src/server/routes/roles.ts index 7c4a17f0..a8809be2 100644 --- a/src/server/routes/roles.ts +++ b/src/server/routes/roles.ts @@ -1,111 +1,236 @@ import { FastifyInstance } from 'fastify' -import { PostgresMeta } from '../../lib' - +import { PostgresMeta } from '../../lib/index.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging } from '../utils.js' +import { + PostgresRoleCreate, + PostgresRoleUpdate, + postgresRoleSchema, + postgresRoleCreateSchema, + postgresRoleUpdateSchema, +} from '../../lib/types.js' +import { Type } from '@sinclair/typebox' export default async (fastify: FastifyInstance) => { fastify.get<{ Headers: { pg: string } Querystring: { include_default_roles?: string - include_system_schemas?: string + limit?: number + offset?: number } - }>('/', async (request, reply) => { - const connectionString = request.headers.pg - const includeDefaultRoles = request.query.include_default_roles === 'true' - const includeSystemSchemas = request.query.include_system_schemas === 'true' + }>( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + querystring: Type.Object({ + include_system_schemas: Type.Optional(Type.String()), + limit: Type.Optional(Type.String()), + offset: Type.Optional(Type.String()), + }), + response: { + 200: Type.Array(postgresRoleSchema), + 500: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const includeDefaultRoles = request.query.include_default_roles === 'true' + const limit = request.query.limit + const offset = request.query.offset - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.roles.list({ includeDefaultRoles, includeSystemSchemas }) - await pgMeta.end() - if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(500) - return { error: error.message } - } + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.roles.list({ + includeDefaultRoles, + limit, + offset, + }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(500) + return { error: error.message } + } - return data - }) + return data + } + ) fastify.get<{ Headers: { pg: string } Params: { id: string } - }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg - const id = Number(request.params.id) + }>( + '/:id(\\d+)', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + params: Type.Object({ + id: Type.RegExp(/\d+/), + }), + response: { + 200: postgresRoleSchema, + 404: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.roles.retrieve({ id }) - await pgMeta.end() - if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(404) - return { error: error.message } - } + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.roles.retrieve({ id }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(404) + return { error: error.message } + } - return data - }) + return data + } + ) fastify.post<{ Headers: { pg: string } - Body: any - }>('/', async (request, reply) => { - const connectionString = request.headers.pg + Body: PostgresRoleCreate + }>( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + body: postgresRoleCreateSchema, + response: { + 200: postgresRoleSchema, + 400: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.roles.create(request.body) - await pgMeta.end() - if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(400) - return { error: error.message } - } + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.roles.create(request.body) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + return { error: error.message } + } - return data - }) + return data + } + ) fastify.patch<{ Headers: { pg: string } Params: { id: string } - Body: any - }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg - const id = Number(request.params.id) + Body: PostgresRoleUpdate + }>( + '/:id(\\d+)', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + params: Type.Object({ + id: Type.RegExp(/\d+/), + }), + body: postgresRoleUpdateSchema, + response: { + 200: postgresRoleSchema, + 400: Type.Object({ + error: Type.String(), + }), + 404: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.roles.update(id, request.body) - await pgMeta.end() - if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(400) - if (error.message.startsWith('Cannot find')) reply.code(404) - return { error: error.message } - } + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.roles.update(id, request.body) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + if (error.message.startsWith('Cannot find')) reply.code(404) + return { error: error.message } + } - return data - }) + return data + } + ) fastify.delete<{ Headers: { pg: string } Params: { id: string } - }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg - const id = Number(request.params.id) + }>( + '/:id(\\d+)', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + params: Type.Object({ + id: Type.RegExp(/\d+/), + }), + querystring: Type.Object({ + cascade: Type.Optional(Type.String()), + }), + response: { + 200: postgresRoleSchema, + 400: Type.Object({ + error: Type.String(), + }), + 404: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.roles.remove(id) - await pgMeta.end() - if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(400) - if (error.message.startsWith('Cannot find')) reply.code(404) - return { error: error.message } - } + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.roles.remove(id) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + if (error.message.startsWith('Cannot find')) reply.code(404) + return { error: error.message } + } - return data - }) + return data + } + ) } diff --git a/src/server/routes/schemas.ts b/src/server/routes/schemas.ts index 2b7552f5..a65d104a 100644 --- a/src/server/routes/schemas.ts +++ b/src/server/routes/schemas.ts @@ -1,29 +1,27 @@ +import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox' import { Type } from '@sinclair/typebox' -import { FastifyInstance } from 'fastify' -import { PostgresMeta } from '../../lib' +import { PostgresMeta } from '../../lib/index.js' import { - PostgresSchemaCreate, - PostgresSchemaUpdate, postgresSchemaSchema, postgresSchemaCreateSchema, postgresSchemaUpdateSchema, -} from '../../lib/types' +} from '../../lib/types.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging } from '../utils.js' -export default async (fastify: FastifyInstance) => { - fastify.get<{ - Headers: { pg: string } - Querystring: { - include_system_schemas?: string - } - }>( +const route: FastifyPluginAsyncTypebox = async (fastify) => { + fastify.get( '/', { schema: { headers: Type.Object({ pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), }), querystring: Type.Object({ - include_system_schemas: Type.Optional(Type.String()), + include_system_schemas: Type.Optional(Type.Boolean()), + limit: Type.Optional(Type.Integer()), + offset: Type.Optional(Type.Integer()), }), response: { 200: Type.Array(postgresSchemaSchema), @@ -34,14 +32,16 @@ export default async (fastify: FastifyInstance) => { }, }, async (request, reply) => { - const connectionString = request.headers.pg - const includeSystemSchemas = request.query.include_system_schemas === 'true' + const config = createConnectionConfig(request) + const includeSystemSchemas = request.query.include_system_schemas + const limit = request.query.limit + const offset = request.query.offset - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.schemas.list({ includeSystemSchemas }) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.schemas.list({ includeSystemSchemas, limit, offset }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(500) return { error: error.message } } @@ -50,20 +50,16 @@ export default async (fastify: FastifyInstance) => { } ) - fastify.get<{ - Headers: { pg: string } - Params: { - id: string - } - }>( + fastify.get( '/:id(\\d+)', { schema: { headers: Type.Object({ pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ - id: Type.RegEx(/\d+/), + id: Type.Integer(), }), response: { 200: postgresSchemaSchema, @@ -74,14 +70,14 @@ export default async (fastify: FastifyInstance) => { }, }, async (request, reply) => { - const connectionString = request.headers.pg - const id = Number(request.params.id) + const config = createConnectionConfig(request) + const id = request.params.id - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.schemas.retrieve({ id }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(404) return { error: error.message } } @@ -90,15 +86,13 @@ export default async (fastify: FastifyInstance) => { } ) - fastify.post<{ - Headers: { pg: string } - Body: PostgresSchemaCreate - }>( + fastify.post( '/', { schema: { headers: Type.Object({ pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), }), body: postgresSchemaCreateSchema, response: { @@ -110,13 +104,13 @@ export default async (fastify: FastifyInstance) => { }, }, async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.schemas.create(request.body) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) return { error: error.message } } @@ -125,21 +119,16 @@ export default async (fastify: FastifyInstance) => { } ) - fastify.patch<{ - Headers: { pg: string } - Params: { - id: string - } - Body: PostgresSchemaUpdate - }>( + fastify.patch( '/:id(\\d+)', { schema: { headers: Type.Object({ pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ - id: Type.RegEx(/\d+/), + id: Type.Integer(), }), body: postgresSchemaUpdateSchema, response: { @@ -154,14 +143,14 @@ export default async (fastify: FastifyInstance) => { }, }, async (request, reply) => { - const connectionString = request.headers.pg - const id = Number(request.params.id) + const config = createConnectionConfig(request) + const id = request.params.id - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.schemas.update(id, request.body) await pgMeta.end() if (error) { - request.log.error({ error, req: request.body }) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) if (error.message.startsWith('Cannot find')) reply.code(404) return { error: error.message } @@ -171,26 +160,19 @@ export default async (fastify: FastifyInstance) => { } ) - fastify.delete<{ - Headers: { pg: string } - Params: { - id: string - } - Querystring: { - cascade?: string - } - }>( + fastify.delete( '/:id(\\d+)', { schema: { headers: Type.Object({ pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ - id: Type.RegEx(/\d+/), + id: Type.Integer(), }), querystring: Type.Object({ - cascade: Type.Optional(Type.String()), + cascade: Type.Optional(Type.Boolean()), }), response: { 200: postgresSchemaSchema, @@ -204,15 +186,15 @@ export default async (fastify: FastifyInstance) => { }, }, async (request, reply) => { - const connectionString = request.headers.pg - const id = Number(request.params.id) - const cascade = request.query.cascade === 'true' + const config = createConnectionConfig(request) + const id = request.params.id + const cascade = request.query.cascade - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.schemas.remove(id, { cascade }) await pgMeta.end() if (error) { - request.log.error({ error, req: request.body }) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) if (error.message.startsWith('Cannot find')) reply.code(404) return { error: error.message } @@ -222,3 +204,4 @@ export default async (fastify: FastifyInstance) => { } ) } +export default route diff --git a/src/server/routes/table-privileges.ts b/src/server/routes/table-privileges.ts new file mode 100644 index 00000000..615d6efa --- /dev/null +++ b/src/server/routes/table-privileges.ts @@ -0,0 +1,134 @@ +import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox' +import { Type } from '@sinclair/typebox' +import { PostgresMeta } from '../../lib/index.js' +import { + postgresTablePrivilegesGrantSchema, + postgresTablePrivilegesRevokeSchema, + postgresTablePrivilegesSchema, +} from '../../lib/types.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging, translateErrorToResponseCode } from '../utils.js' + +const route: FastifyPluginAsyncTypebox = async (fastify) => { + fastify.get( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + querystring: Type.Object({ + include_system_schemas: Type.Optional(Type.Boolean()), + // Note: this only supports comma separated values (e.g., "...?included_schemas=public,core") + included_schemas: Type.Optional(Type.String()), + excluded_schemas: Type.Optional(Type.String()), + limit: Type.Optional(Type.Integer()), + offset: Type.Optional(Type.Integer()), + }), + response: { + 200: Type.Array(postgresTablePrivilegesSchema), + 500: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const includeSystemSchemas = request.query.include_system_schemas + const includedSchemas = request.query.included_schemas?.split(',') + const excludedSchemas = request.query.excluded_schemas?.split(',') + const limit = request.query.limit + const offset = request.query.offset + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.tablePrivileges.list({ + includeSystemSchemas, + includedSchemas, + excludedSchemas, + limit, + offset, + }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(translateErrorToResponseCode(error, 500)) + return { error: error.message } + } + + return data + } + ) + + fastify.post( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + body: Type.Array(postgresTablePrivilegesGrantSchema), + response: { + 200: Type.Array(postgresTablePrivilegesSchema), + 400: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.tablePrivileges.grant(request.body) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + return { error: error.message } + } + + return data + } + ) + + fastify.delete( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + body: Type.Array(postgresTablePrivilegesRevokeSchema), + response: { + 200: Type.Array(postgresTablePrivilegesSchema), + 400: Type.Object({ + error: Type.String(), + }), + 404: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.tablePrivileges.revoke(request.body) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + if (error.message.startsWith('Cannot find')) reply.code(404) + return { error: error.message } + } + + return data + } + ) +} +export default route diff --git a/src/server/routes/tables.ts b/src/server/routes/tables.ts index 608f6ddb..ff5cb2c9 100644 --- a/src/server/routes/tables.ts +++ b/src/server/routes/tables.ts @@ -1,113 +1,223 @@ -import { FastifyInstance } from 'fastify' -import { PostgresMeta } from '../../lib' +import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox' +import { Type } from '@sinclair/typebox' +import { PostgresMeta } from '../../lib/index.js' +import { + postgresTableCreateSchema, + postgresTableSchema, + postgresTableUpdateSchema, +} from '../../lib/types.js' +import { + createConnectionConfig, + extractRequestForLogging, + translateErrorToResponseCode, +} from '../utils.js' -export default async (fastify: FastifyInstance) => { - fastify.get<{ - Headers: { pg: string } - Querystring: { - include_system_schemas?: string - } - }>('/', async (request, reply) => { - const connectionString = request.headers.pg - const includeSystemSchemas = request.query.include_system_schemas === 'true' +const route: FastifyPluginAsyncTypebox = async (fastify) => { + fastify.get( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + querystring: Type.Object({ + include_system_schemas: Type.Optional(Type.Boolean()), + // Note: this only supports comma separated values (e.g., ".../tables?included_schemas=public,core") + included_schemas: Type.Optional(Type.String()), + excluded_schemas: Type.Optional(Type.String()), + limit: Type.Optional(Type.Integer()), + offset: Type.Optional(Type.Integer()), + include_columns: Type.Optional(Type.Boolean()), + }), + response: { + 200: Type.Array(postgresTableSchema), + 500: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const includeSystemSchemas = request.query.include_system_schemas + const includedSchemas = request.query.included_schemas?.split(',') + const excludedSchemas = request.query.excluded_schemas?.split(',') + const limit = request.query.limit + const offset = request.query.offset + const includeColumns = request.query.include_columns + + const config = createConnectionConfig(request) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.tables.list({ + includeSystemSchemas, + includedSchemas, + excludedSchemas, + limit, + offset, + includeColumns, + }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(translateErrorToResponseCode(error, 500)) + return { error: error.message } + } - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.tables.list({ includeSystemSchemas }) - await pgMeta.end() - if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(500) - return { error: error.message } + return data } + ) - return data - }) + fastify.get( + '/:id(\\d+)', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + params: Type.Object({ + id: Type.Integer(), + }), + response: { + 200: postgresTableSchema, + 404: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const id = request.params.id - fastify.get<{ - Headers: { pg: string } - Params: { - id: string - } - }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg - const id = Number(request.params.id) + const config = createConnectionConfig(request) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.tables.retrieve({ id }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(404) + return { error: error.message } + } - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.tables.retrieve({ id }) - await pgMeta.end() - if (error) { - request.log.error({ error, req: request.body }) - reply.code(404) - return { error: error.message } + return data } + ) - return data - }) - - fastify.post<{ - Headers: { pg: string } - Body: any - }>('/', async (request, reply) => { - const connectionString = request.headers.pg + fastify.post( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + body: postgresTableCreateSchema, + response: { + 200: postgresTableSchema, + 400: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.tables.create(request.body) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + return { error: error.message } + } - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.tables.create(request.body) - await pgMeta.end() - if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(400) - return { error: error.message } + return data } + ) - return data - }) + fastify.patch( + '/:id(\\d+)', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + params: Type.Object({ + id: Type.Integer(), + }), + body: postgresTableUpdateSchema, + response: { + 200: postgresTableSchema, + 400: Type.Object({ + error: Type.String(), + }), + 404: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const id = request.params.id - fastify.patch<{ - Headers: { pg: string } - Params: { - id: string - } - Body: any - }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg - const id = Number(request.params.id) + const config = createConnectionConfig(request) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.tables.update(id, request.body) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + if (error.message.startsWith('Cannot find')) reply.code(404) + return { error: error.message } + } - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.tables.update(id, request.body) - await pgMeta.end() - if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(400) - if (error.message.startsWith('Cannot find')) reply.code(404) - return { error: error.message } + return data } + ) - return data - }) + fastify.delete( + '/:id(\\d+)', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + params: Type.Object({ + id: Type.Integer(), + }), + querystring: Type.Object({ + cascade: Type.Optional(Type.Boolean()), + }), + response: { + 200: postgresTableSchema, + 400: Type.Object({ + error: Type.String(), + }), + 404: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const id = request.params.id + const cascade = request.query.cascade - fastify.delete<{ - Headers: { pg: string } - Params: { - id: string - } - Querystring: { - cascade?: string - } - }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg - const id = Number(request.params.id) - const cascade = request.query.cascade === 'true' + const config = createConnectionConfig(request) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.tables.remove(id, { cascade }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(400) + if (error.message.startsWith('Cannot find')) reply.code(404) + return { error: error.message } + } - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.tables.remove(id, { cascade }) - await pgMeta.end() - if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) - reply.code(400) - if (error.message.startsWith('Cannot find')) reply.code(404) - return { error: error.message } + return data } - - return data - }) + ) } +export default route diff --git a/src/server/routes/triggers.ts b/src/server/routes/triggers.ts index 61bace13..3eb2212a 100644 --- a/src/server/routes/triggers.ts +++ b/src/server/routes/triggers.ts @@ -1,17 +1,38 @@ import { FastifyInstance } from 'fastify' -import { PostgresMeta } from '../../lib' +import { PostgresMeta } from '../../lib/index.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } + Querystring: { + include_system_schemas?: string + // Note: this only supports comma separated values (e.g., ".../columns?included_schemas=public,core") + included_schemas?: string + excluded_schemas?: string + limit?: number + offset?: number + } }>('/', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) + const includeSystemSchemas = request.query.include_system_schemas === 'true' + const includedSchemas = request.query.included_schemas?.split(',') + const excludedSchemas = request.query.excluded_schemas?.split(',') + const limit = request.query.limit + const offset = request.query.offset - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.triggers.list() + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.triggers.list({ + includeSystemSchemas, + includedSchemas, + excludedSchemas, + limit, + offset, + }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(500) return { error: error.message } } @@ -20,19 +41,19 @@ export default async (fastify: FastifyInstance) => { }) fastify.get<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.triggers.retrieve({ id }) await pgMeta.end() if (error) { - request.log.error({ error, req: request.body }) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(404) return { error: error.message } } @@ -41,16 +62,16 @@ export default async (fastify: FastifyInstance) => { }) fastify.post<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Body: any }>('/', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.triggers.create(request.body) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.triggers.create(request.body as any) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) return { error: error.message } } @@ -59,20 +80,20 @@ export default async (fastify: FastifyInstance) => { }) fastify.patch<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { id: string } Body: any }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const id = Number(request.params.id) - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.triggers.update(id, request.body) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.triggers.update(id, request.body as any) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) if (error.message.startsWith('Cannot find')) reply.code(404) return { error: error.message } @@ -82,7 +103,7 @@ export default async (fastify: FastifyInstance) => { }) fastify.delete<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Params: { id: string } @@ -90,15 +111,15 @@ export default async (fastify: FastifyInstance) => { cascade?: string } }>('/:id(\\d+)', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) const id = Number(request.params.id) const cascade = request.query.cascade === 'true' - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) + const pgMeta = new PostgresMeta(config) const { data, error } = await pgMeta.triggers.remove(id, { cascade }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(400) if (error.message.startsWith('Cannot find')) reply.code(404) return { error: error.message } diff --git a/src/server/routes/types.ts b/src/server/routes/types.ts index 87baf407..c4e0dfbd 100644 --- a/src/server/routes/types.ts +++ b/src/server/routes/types.ts @@ -1,21 +1,41 @@ import { FastifyInstance } from 'fastify' -import { PostgresMeta } from '../../lib' +import { PostgresMeta } from '../../lib/index.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string } + Headers: { pg: string; 'x-pg-application-name'?: string } Querystring: { + include_array_types?: string include_system_schemas?: string + // Note: this only supports comma separated values (e.g., ".../types?included_schemas=public,core") + included_schemas?: string + excluded_schemas?: string + limit?: number + offset?: number } }>('/', async (request, reply) => { - const connectionString = request.headers.pg + const config = createConnectionConfig(request) + const includeArrayTypes = request.query.include_array_types === 'true' const includeSystemSchemas = request.query.include_system_schemas === 'true' + const includedSchemas = request.query.included_schemas?.split(',') + const excludedSchemas = request.query.excluded_schemas?.split(',') + const limit = request.query.limit + const offset = request.query.offset - const pgMeta = new PostgresMeta({ connectionString, max: 1 }) - const { data, error } = await pgMeta.types.list({ includeSystemSchemas }) + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.types.list({ + includeArrayTypes, + includeSystemSchemas, + includedSchemas, + excludedSchemas, + limit, + offset, + }) await pgMeta.end() if (error) { - request.log.error(JSON.stringify({ error, req: request.body })) + request.log.error({ error, request: extractRequestForLogging(request) }) reply.code(500) return { error: error.message } } diff --git a/src/server/routes/views.ts b/src/server/routes/views.ts new file mode 100644 index 00000000..119088e3 --- /dev/null +++ b/src/server/routes/views.ts @@ -0,0 +1,99 @@ +import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox' +import { Type } from '@sinclair/typebox' +import { PostgresMeta } from '../../lib/index.js' +import { postgresViewSchema } from '../../lib/types.js' +import { createConnectionConfig } from '../utils.js' +import { extractRequestForLogging } from '../utils.js' + +const route: FastifyPluginAsyncTypebox = async (fastify) => { + fastify.get( + '/', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + querystring: Type.Object({ + include_system_schemas: Type.Optional(Type.Boolean()), + // Note: this only supports comma separated values (e.g., ".../views?included_schemas=public,core") + included_schemas: Type.Optional(Type.String()), + excluded_schemas: Type.Optional(Type.String()), + limit: Type.Optional(Type.Integer()), + offset: Type.Optional(Type.Integer()), + include_columns: Type.Optional(Type.Boolean()), + }), + response: { + 200: Type.Array(postgresViewSchema), + 500: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const includeSystemSchemas = request.query.include_system_schemas + const includedSchemas = request.query.included_schemas?.split(',') + const excludedSchemas = request.query.excluded_schemas?.split(',') + const limit = request.query.limit + const offset = request.query.offset + const includeColumns = request.query.include_columns + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.views.list({ + includeSystemSchemas, + includedSchemas, + excludedSchemas, + limit, + offset, + includeColumns, + }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(500) + return { error: error.message } + } + + return data + } + ) + + fastify.get( + '/:id(\\d+)', + { + schema: { + headers: Type.Object({ + pg: Type.String(), + 'x-pg-application-name': Type.Optional(Type.String()), + }), + params: Type.Object({ + id: Type.Integer(), + }), + response: { + 200: postgresViewSchema, + 404: Type.Object({ + error: Type.String(), + }), + }, + }, + }, + async (request, reply) => { + const config = createConnectionConfig(request) + const id = request.params.id + + const pgMeta = new PostgresMeta(config) + const { data, error } = await pgMeta.views.retrieve({ id }) + await pgMeta.end() + if (error) { + request.log.error({ error, request: extractRequestForLogging(request) }) + reply.code(404) + return { error: error.message } + } + + return data + } + ) +} +export default route diff --git a/src/server/sentry.ts b/src/server/sentry.ts new file mode 100644 index 00000000..fa50f0a3 --- /dev/null +++ b/src/server/sentry.ts @@ -0,0 +1,50 @@ +import * as Sentry from '@sentry/node' +import { nodeProfilingIntegration } from '@sentry/profiling-node' + +const sentryEnvironment = process.env.ENVIRONMENT ?? 'local' +const dsn = process.env.SENTRY_DSN ?? '' + +const captureOptions: Sentry.NodeOptions = + sentryEnvironment === 'prod' + ? { + // Tracing + tracesSampleRate: 0.00001, // trace 1/10k events + // Set sampling rate for profiling - this is evaluated only once per SDK.init call + profilesSampleRate: 0.00001, // profile 1/10k events + } + : { + tracesSampleRate: 0.01, // trace 1% of the events + profilesSampleRate: 0.01, + } + +const sensitiveKeys = ['pg', 'x-connection-encrypted'] + +function redactSensitiveData(data: any) { + if (data && typeof data === 'object') { + for (const key of sensitiveKeys) { + if (key in data) { + data[key] = '[REDACTED]' + } + } + } +} + +export default Sentry.init({ + enabled: Boolean(dsn), + dsn: dsn, + environment: sentryEnvironment, + integrations: [nodeProfilingIntegration()], + beforeSendTransaction(transaction) { + if (transaction.contexts?.trace?.data) { + redactSensitiveData(transaction.contexts.trace.data) + } + return transaction + }, + beforeSendSpan(span) { + if (span.data) { + redactSensitiveData(span.data) + } + return span + }, + ...captureOptions, +}) diff --git a/src/server/server.ts b/src/server/server.ts new file mode 100644 index 00000000..68fbb54c --- /dev/null +++ b/src/server/server.ts @@ -0,0 +1,216 @@ +import closeWithGrace from 'close-with-grace' +import { pino } from 'pino' +import { PostgresMeta } from '../lib/index.js' +import { build as buildApp } from './app.js' +import { build as buildAdminApp } from './admin-app.js' +import { + DEFAULT_POOL_CONFIG, + EXPORT_DOCS, + GENERATE_TYPES, + GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS, + GENERATE_TYPES_INCLUDED_SCHEMAS, + GENERATE_TYPES_SWIFT_ACCESS_CONTROL, + PG_CONNECTION, + PG_META_HOST, + PG_META_PORT, + POSTGREST_VERSION, +} from './constants.js' +import { apply as applyTypescriptTemplate } from './templates/typescript.js' +import { apply as applyGoTemplate } from './templates/go.js' +import { apply as applySwiftTemplate } from './templates/swift.js' +import { apply as applyPythonTemplate } from './templates/python.js' + +const logger = pino({ + formatters: { + level(label) { + return { level: label } + }, + }, + timestamp: pino.stdTimeFunctions.isoTime, +}) + +const app = buildApp({ logger }) +const adminApp = buildAdminApp({ logger }) + +async function getTypeOutput(): Promise { + const pgMeta: PostgresMeta = new PostgresMeta({ + ...DEFAULT_POOL_CONFIG, + connectionString: PG_CONNECTION, + }) + const [ + { data: schemas, error: schemasError }, + { data: tables, error: tablesError }, + { data: foreignTables, error: foreignTablesError }, + { data: views, error: viewsError }, + { data: materializedViews, error: materializedViewsError }, + { data: columns, error: columnsError }, + { data: relationships, error: relationshipsError }, + { data: functions, error: functionsError }, + { data: types, error: typesError }, + ] = await Promise.all([ + pgMeta.schemas.list(), + pgMeta.tables.list({ + includedSchemas: + GENERATE_TYPES_INCLUDED_SCHEMAS.length > 0 ? GENERATE_TYPES_INCLUDED_SCHEMAS : undefined, + includeColumns: false, + }), + pgMeta.foreignTables.list({ + includedSchemas: + GENERATE_TYPES_INCLUDED_SCHEMAS.length > 0 ? GENERATE_TYPES_INCLUDED_SCHEMAS : undefined, + includeColumns: false, + }), + pgMeta.views.list({ + includedSchemas: + GENERATE_TYPES_INCLUDED_SCHEMAS.length > 0 ? GENERATE_TYPES_INCLUDED_SCHEMAS : undefined, + includeColumns: false, + }), + pgMeta.materializedViews.list({ + includedSchemas: + GENERATE_TYPES_INCLUDED_SCHEMAS.length > 0 ? GENERATE_TYPES_INCLUDED_SCHEMAS : undefined, + includeColumns: false, + }), + pgMeta.columns.list({ + includedSchemas: + GENERATE_TYPES_INCLUDED_SCHEMAS.length > 0 ? GENERATE_TYPES_INCLUDED_SCHEMAS : undefined, + }), + pgMeta.relationships.list(), + pgMeta.functions.list({ + includedSchemas: + GENERATE_TYPES_INCLUDED_SCHEMAS.length > 0 ? GENERATE_TYPES_INCLUDED_SCHEMAS : undefined, + }), + pgMeta.types.list({ + includeTableTypes: true, + includeArrayTypes: true, + includeSystemSchemas: true, + }), + ]) + await pgMeta.end() + + if (schemasError) { + throw new Error(schemasError.message) + } + if (tablesError) { + throw new Error(tablesError.message) + } + if (foreignTablesError) { + throw new Error(foreignTablesError.message) + } + if (viewsError) { + throw new Error(viewsError.message) + } + if (materializedViewsError) { + throw new Error(materializedViewsError.message) + } + if (columnsError) { + throw new Error(columnsError.message) + } + if (relationshipsError) { + throw new Error(relationshipsError.message) + } + if (functionsError) { + throw new Error(functionsError.message) + } + if (typesError) { + throw new Error(typesError.message) + } + + const config = { + schemas: schemas!.filter( + ({ name }) => + GENERATE_TYPES_INCLUDED_SCHEMAS.length === 0 || + GENERATE_TYPES_INCLUDED_SCHEMAS.includes(name) + ), + tables: tables!, + foreignTables: foreignTables!, + views: views!, + materializedViews: materializedViews!, + columns: columns!, + relationships: relationships!, + functions: functions!.filter( + ({ return_type }) => !['trigger', 'event_trigger'].includes(return_type) + ), + types: types!, + detectOneToOneRelationships: GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS, + postgrestVersion: POSTGREST_VERSION, + } + + switch (GENERATE_TYPES?.toLowerCase()) { + case 'typescript': + return await applyTypescriptTemplate(config) + case 'swift': + return await applySwiftTemplate({ + ...config, + accessControl: GENERATE_TYPES_SWIFT_ACCESS_CONTROL, + }) + case 'go': + return applyGoTemplate(config) + case 'python': + return applyPythonTemplate(config) + default: + throw new Error(`Unsupported language for GENERATE_TYPES: ${GENERATE_TYPES}`) + } +} + +if (EXPORT_DOCS) { + // TODO: Move to a separate script. + await app.ready() + // @ts-ignore: app.swagger() is a Fastify decorator, so doesn't show up in the types + console.log(JSON.stringify(app.swagger(), null, 2)) +} else if (GENERATE_TYPES) { + console.log(await getTypeOutput()) +} else { + const closeListeners = closeWithGrace(async ({ err, signal, manual }) => { + if (err) { + app.log.error({ err }, 'server closing with error') + } else { + app.log.error( + { err: new Error('Signal Received') }, + `${signal} signal received, server closing, close manual received: ${manual}` + ) + } + try { + await app.close() + } catch (err) { + app.log.error({ err }, `Failed to close app`) + throw err + } + try { + await adminApp.close() + } catch (err) { + app.log.error({ err }, `Failed to close adminApp`) + throw err + } + }) + app.addHook('onClose', async () => { + try { + closeListeners.uninstall() + await adminApp.close() + } catch (err) { + app.log.error({ err }, `Failed to close adminApp in app onClose hook`) + throw err + } + }) + adminApp.addHook('onClose', async () => { + try { + closeListeners.uninstall() + await app.close() + } catch (err) { + app.log.error({ err }, `Failed to close app in adminApp onClose hook`) + throw err + } + }) + + app.listen({ port: PG_META_PORT, host: PG_META_HOST }, (err) => { + if (err) { + app.log.error({ err }, 'Uncaught error in app, exit(1)') + process.exit(1) + } + const adminPort = PG_META_PORT + 1 + adminApp.listen({ port: adminPort, host: PG_META_HOST }, (err) => { + if (err) { + app.log.error({ err }, 'Uncaught error in adminApp, exit(1)') + process.exit(1) + } + }) + }) +} diff --git a/src/server/templates/go.ts b/src/server/templates/go.ts new file mode 100644 index 00000000..46fd5d82 --- /dev/null +++ b/src/server/templates/go.ts @@ -0,0 +1,323 @@ +import type { + PostgresColumn, + PostgresMaterializedView, + PostgresSchema, + PostgresTable, + PostgresType, + PostgresView, +} from '../../lib/index.js' +import type { GeneratorMetadata } from '../../lib/generators.js' + +type Operation = 'Select' | 'Insert' | 'Update' + +export const apply = ({ + schemas, + tables, + views, + materializedViews, + columns, + types, +}: GeneratorMetadata): string => { + const columnsByTableId = columns + .sort(({ name: a }, { name: b }) => a.localeCompare(b)) + .reduce( + (acc, curr) => { + acc[curr.table_id] ??= [] + acc[curr.table_id].push(curr) + return acc + }, + {} as Record + ) + + const compositeTypes = types.filter((type) => type.attributes.length > 0) + + let output = ` +package database + +${tables + .filter((table) => schemas.some((schema) => schema.name === table.schema)) + .flatMap((table) => + generateTableStructsForOperations( + schemas.find((schema) => schema.name === table.schema)!, + table, + columnsByTableId[table.id], + types, + ['Select', 'Insert', 'Update'] + ) + ) + .join('\n\n')} + +${views + .filter((view) => schemas.some((schema) => schema.name === view.schema)) + .flatMap((view) => + generateTableStructsForOperations( + schemas.find((schema) => schema.name === view.schema)!, + view, + columnsByTableId[view.id], + types, + ['Select'] + ) + ) + .join('\n\n')} + +${materializedViews + .filter((materializedView) => schemas.some((schema) => schema.name === materializedView.schema)) + .flatMap((materializedView) => + generateTableStructsForOperations( + schemas.find((schema) => schema.name === materializedView.schema)!, + materializedView, + columnsByTableId[materializedView.id], + types, + ['Select'] + ) + ) + .join('\n\n')} + +${compositeTypes + .filter((compositeType) => schemas.some((schema) => schema.name === compositeType.schema)) + .map((compositeType) => + generateCompositeTypeStruct( + schemas.find((schema) => schema.name === compositeType.schema)!, + compositeType, + types + ) + ) + .join('\n\n')} +`.trim() + + return output +} + +/** + * Converts a Postgres name to PascalCase. + * + * @example + * ```ts + * formatForGoTypeName('pokedex') // Pokedex + * formatForGoTypeName('pokemon_center') // PokemonCenter + * formatForGoTypeName('victory-road') // VictoryRoad + * formatForGoTypeName('pokemon league') // PokemonLeague + * ``` + */ +function formatForGoTypeName(name: string): string { + return name + .split(/[^a-zA-Z0-9]/) + .map((word) => `${word[0].toUpperCase()}${word.slice(1)}`) + .join('') +} + +function generateTableStruct( + schema: PostgresSchema, + table: PostgresTable | PostgresView | PostgresMaterializedView, + columns: PostgresColumn[] | undefined, + types: PostgresType[], + operation: Operation +): string { + // Storing columns as a tuple of [formattedName, type, name] rather than creating the string + // representation of the line allows us to pre-format the entries. Go formats + // struct fields to be aligned, e.g.: + // ```go + // type Pokemon struct { + // id int `json:"id"` + // name string `json:"name"` + // } + const columnEntries: [string, string, string][] = + columns?.map((column) => { + let nullable: boolean + if (operation === 'Insert') { + nullable = + column.is_nullable || column.is_identity || column.is_generated || !!column.default_value + } else if (operation === 'Update') { + nullable = true + } else { + nullable = column.is_nullable + } + return [ + formatForGoTypeName(column.name), + pgTypeToGoType(column.format, nullable, types), + column.name, + ] + }) ?? [] + + const [maxFormattedNameLength, maxTypeLength] = columnEntries.reduce( + ([maxFormattedName, maxType], [formattedName, type]) => { + return [Math.max(maxFormattedName, formattedName.length), Math.max(maxType, type.length)] + }, + [0, 0] + ) + + // Pad the formatted name and type to align the struct fields, then join + // create the final string representation of the struct fields. + const formattedColumnEntries = columnEntries.map(([formattedName, type, name]) => { + return ` ${formattedName.padEnd(maxFormattedNameLength)} ${type.padEnd( + maxTypeLength + )} \`json:"${name}"\`` + }) + + return ` +type ${formatForGoTypeName(schema.name)}${formatForGoTypeName(table.name)}${operation} struct { +${formattedColumnEntries.join('\n')} +} +`.trim() +} + +function generateTableStructsForOperations( + schema: PostgresSchema, + table: PostgresTable | PostgresView | PostgresMaterializedView, + columns: PostgresColumn[] | undefined, + types: PostgresType[], + operations: Operation[] +): string[] { + return operations.map((operation) => + generateTableStruct(schema, table, columns, types, operation) + ) +} + +function generateCompositeTypeStruct( + schema: PostgresSchema, + type: PostgresType, + types: PostgresType[] +): string { + // Use the type_id of the attributes to find the types of the attributes + const typeWithRetrievedAttributes = { + ...type, + attributes: type.attributes.map((attribute) => { + const type = types.find((type) => type.id === attribute.type_id) + return { + ...attribute, + type, + } + }), + } + const attributeEntries: [string, string, string][] = typeWithRetrievedAttributes.attributes.map( + (attribute) => [ + formatForGoTypeName(attribute.name), + pgTypeToGoType(attribute.type!.format, false), + attribute.name, + ] + ) + + const [maxFormattedNameLength, maxTypeLength] = attributeEntries.reduce( + ([maxFormattedName, maxType], [formattedName, type]) => { + return [Math.max(maxFormattedName, formattedName.length), Math.max(maxType, type.length)] + }, + [0, 0] + ) + + // Pad the formatted name and type to align the struct fields, then join + // create the final string representation of the struct fields. + const formattedAttributeEntries = attributeEntries.map(([formattedName, type, name]) => { + return ` ${formattedName.padEnd(maxFormattedNameLength)} ${type.padEnd( + maxTypeLength + )} \`json:"${name}"\`` + }) + + return ` +type ${formatForGoTypeName(schema.name)}${formatForGoTypeName(type.name)} struct { +${formattedAttributeEntries.join('\n')} +} +`.trim() +} + +// Note: the type map uses `interface{ } `, not `any`, to remain compatible with +// older versions of Go. +const GO_TYPE_MAP = { + // Bool + bool: 'bool', + + // Numbers + int2: 'int16', + int4: 'int32', + int8: 'int64', + float4: 'float32', + float8: 'float64', + numeric: 'float64', + + // Strings + bytea: '[]byte', + bpchar: 'string', + varchar: 'string', + date: 'string', + text: 'string', + citext: 'string', + time: 'string', + timetz: 'string', + timestamp: 'string', + timestamptz: 'string', + uuid: 'string', + vector: 'string', + + // JSON + json: 'interface{}', + jsonb: 'interface{}', + + // Range + int4range: 'string', + int4multirange: 'string', + int8range: 'string', + int8multirange: 'string', + numrange: 'string', + nummultirange: 'string', + tsrange: 'string', + tsmultirange: 'string', + tstzrange: 'string', + tstzmultirange: 'string', + daterange: 'string', + datemultirange: 'string', + + // Misc + void: 'interface{}', + record: 'map[string]interface{}', +} as const + +type GoType = (typeof GO_TYPE_MAP)[keyof typeof GO_TYPE_MAP] + +const GO_NULLABLE_TYPE_MAP: Record = { + string: '*string', + bool: '*bool', + int16: '*int16', + int32: '*int32', + int64: '*int64', + float32: '*float32', + float64: '*float64', + '[]byte': '[]byte', + 'interface{}': 'interface{}', + 'map[string]interface{}': 'map[string]interface{}', +} + +function pgTypeToGoType(pgType: string, nullable: boolean, types: PostgresType[] = []): string { + let goType: GoType | undefined = undefined + if (pgType in GO_TYPE_MAP) { + goType = GO_TYPE_MAP[pgType as keyof typeof GO_TYPE_MAP] + } + + // Enums + const enumType = types.find((type) => type.name === pgType && type.enums.length > 0) + if (enumType) { + goType = 'string' + } + + if (goType) { + if (nullable) { + return GO_NULLABLE_TYPE_MAP[goType] + } + return goType + } + + // Composite types + const compositeType = types.find((type) => type.name === pgType && type.attributes.length > 0) + if (compositeType) { + // TODO: generate composite types + // return formatForGoTypeName(pgType) + return 'map[string]interface{}' + } + + // Arrays + if (pgType.startsWith('_')) { + const innerType = pgTypeToGoType(pgType.slice(1), nullable) + return `[]${innerType} ` + } + + // Fallback + return 'interface{}' +} diff --git a/src/server/templates/python.ts b/src/server/templates/python.ts new file mode 100644 index 00000000..2d9459ca --- /dev/null +++ b/src/server/templates/python.ts @@ -0,0 +1,412 @@ +import type { + PostgresColumn, + PostgresMaterializedView, + PostgresSchema, + PostgresTable, + PostgresType, + PostgresView, +} from '../../lib/index.js' +import type { GeneratorMetadata } from '../../lib/generators.js' + +export const apply = ({ + schemas, + tables, + views, + materializedViews, + columns, + types, +}: GeneratorMetadata): string => { + const ctx = new PythonContext(types, columns, schemas) + // Used for efficient lookup of types by schema name + const schemasNames = new Set(schemas.map((schema) => schema.name)) + const py_tables = tables.flatMap((table) => { + const py_class_and_methods = ctx.tableToClass(table) + return py_class_and_methods + }) + const composite_types = types + // We always include system schemas, so we need to filter out types that are not in the included schemas + .filter((type) => type.attributes.length > 0 && schemasNames.has(type.schema)) + .map((type) => ctx.typeToClass(type)) + const py_views = views.map((view) => ctx.viewToClass(view)) + const py_matviews = materializedViews.map((matview) => ctx.matViewToClass(matview)) + + let output = ` +from __future__ import annotations + +import datetime +import uuid +from typing import ( + Annotated, + Any, + List, + Literal, + NotRequired, + Optional, + TypeAlias, + TypedDict, +) + +from pydantic import BaseModel, Field, Json + +${concatLines(Object.values(ctx.user_enums))} + +${concatLines(py_tables)} + +${concatLines(py_views)} + +${concatLines(py_matviews)} + +${concatLines(composite_types)} + +`.trim() + + return output +} + +interface Serializable { + serialize(): string +} + +class PythonContext { + types: { [k: string]: PostgresType } + user_enums: { [k: string]: PythonEnum } + columns: Record + schemas: { [k: string]: PostgresSchema } + + constructor(types: PostgresType[], columns: PostgresColumn[], schemas: PostgresSchema[]) { + this.schemas = Object.fromEntries(schemas.map((schema) => [schema.name, schema])) + this.types = Object.fromEntries(types.map((type) => [type.name, type])) + this.columns = columns + .sort(({ name: a }, { name: b }) => a.localeCompare(b)) + .reduce( + (acc, curr) => { + acc[curr.table_id] ??= [] + acc[curr.table_id].push(curr) + return acc + }, + {} as Record + ) + this.user_enums = Object.fromEntries( + types.filter((type) => type.enums.length > 0).map((type) => [type.name, new PythonEnum(type)]) + ) + } + + resolveTypeName(name: string): string { + if (name in this.user_enums) { + return this.user_enums[name].name + } + if (name in PY_TYPE_MAP) { + return PY_TYPE_MAP[name] + } + if (name in this.types) { + const type = this.types[name] + const schema = type!.schema + return `${formatForPyClassName(schema)}${formatForPyClassName(type.name)}` + } + return 'Any' + } + + parsePgType(pg_type: string): PythonType { + if (pg_type.startsWith('_')) { + const inner_str = pg_type.slice(1) + const inner = this.parsePgType(inner_str) + return new PythonListType(inner) + } else { + const type_name = this.resolveTypeName(pg_type) + return new PythonSimpleType(type_name) + } + } + + typeToClass(type: PostgresType): PythonBaseModel { + const types = Object.values(this.types) + const attributes = type.attributes.map((attribute) => { + const type = types.find((type) => type.id === attribute.type_id) + return { + ...attribute, + type, + } + }) + const attributeEntries: PythonBaseModelAttr[] = attributes.map((attribute) => { + const type = this.parsePgType(attribute.type!.name) + return new PythonBaseModelAttr(attribute.name, type, false) + }) + + const schema = this.schemas[type.schema] + return new PythonBaseModel(type.name, schema, attributeEntries) + } + + columnsToClassAttrs(table_id: number): PythonBaseModelAttr[] { + const attrs = this.columns[table_id] ?? [] + return attrs.map((col) => { + const type = this.parsePgType(col.format) + return new PythonBaseModelAttr(col.name, type, col.is_nullable) + }) + } + + columnsToDictAttrs(table_id: number, not_required: boolean): PythonTypedDictAttr[] { + const attrs = this.columns[table_id] ?? [] + return attrs.map((col) => { + const type = this.parsePgType(col.format) + return new PythonTypedDictAttr( + col.name, + type, + col.is_nullable, + not_required || col.is_nullable || col.is_identity || col.default_value !== null + ) + }) + } + + tableToClass(table: PostgresTable): [PythonBaseModel, PythonTypedDict, PythonTypedDict] { + const schema = this.schemas[table.schema] + const select = new PythonBaseModel(table.name, schema, this.columnsToClassAttrs(table.id)) + const insert = new PythonTypedDict( + table.name, + 'Insert', + schema, + this.columnsToDictAttrs(table.id, false) + ) + const update = new PythonTypedDict( + table.name, + 'Update', + schema, + this.columnsToDictAttrs(table.id, true) + ) + return [select, insert, update] + } + + viewToClass(view: PostgresView): PythonBaseModel { + const attributes = this.columnsToClassAttrs(view.id) + return new PythonBaseModel(view.name, this.schemas[view.schema], attributes) + } + + matViewToClass(matview: PostgresMaterializedView): PythonBaseModel { + const attributes = this.columnsToClassAttrs(matview.id) + return new PythonBaseModel(matview.name, this.schemas[matview.schema], attributes) + } +} + +class PythonEnum implements Serializable { + name: string + variants: string[] + constructor(type: PostgresType) { + this.name = `${formatForPyClassName(type.schema)}${formatForPyClassName(type.name)}` + this.variants = type.enums + } + serialize(): string { + const variants = this.variants.map((item) => `"${item}"`).join(', ') + return `${this.name}: TypeAlias = Literal[${variants}]` + } +} + +type PythonType = PythonListType | PythonSimpleType + +class PythonSimpleType implements Serializable { + name: string + constructor(name: string) { + this.name = name + } + serialize(): string { + return this.name + } +} + +class PythonListType implements Serializable { + inner: PythonType + constructor(inner: PythonType) { + this.inner = inner + } + serialize(): string { + return `List[${this.inner.serialize()}]` + } +} + +class PythonBaseModelAttr implements Serializable { + name: string + pg_name: string + py_type: PythonType + nullable: boolean + + constructor(name: string, py_type: PythonType, nullable: boolean) { + this.name = formatForPyAttributeName(name) + this.pg_name = name + this.py_type = py_type + this.nullable = nullable + } + + serialize(): string { + const py_type = this.nullable + ? `Optional[${this.py_type.serialize()}]` + : this.py_type.serialize() + return ` ${this.name}: ${py_type} = Field(alias="${this.pg_name}")` + } +} + +class PythonBaseModel implements Serializable { + name: string + table_name: string + schema: PostgresSchema + class_attributes: PythonBaseModelAttr[] + + constructor(name: string, schema: PostgresSchema, class_attributes: PythonBaseModelAttr[]) { + this.schema = schema + this.class_attributes = class_attributes + this.table_name = name + this.name = `${formatForPyClassName(schema.name)}${formatForPyClassName(name)}` + } + serialize(): string { + const attributes = + this.class_attributes.length > 0 + ? this.class_attributes.map((attr) => attr.serialize()).join('\n') + : ' pass' + return `class ${this.name}(BaseModel):\n${attributes}` + } +} + +class PythonTypedDictAttr implements Serializable { + name: string + pg_name: string + py_type: PythonType + nullable: boolean + not_required: boolean + + constructor(name: string, py_type: PythonType, nullable: boolean, required: boolean) { + this.name = formatForPyAttributeName(name) + this.pg_name = name + this.py_type = py_type + this.nullable = nullable + this.not_required = required + } + + serialize(): string { + const annotation = `Annotated[${this.py_type.serialize()}, Field(alias="${this.pg_name}")]` + const rhs = this.not_required ? `NotRequired[${annotation}]` : annotation + return ` ${this.name}: ${rhs}` + } +} + +class PythonTypedDict implements Serializable { + name: string + table_name: string + parent_class: string + schema: PostgresSchema + dict_attributes: PythonTypedDictAttr[] + operation: 'Insert' | 'Update' + + constructor( + name: string, + operation: 'Insert' | 'Update', + schema: PostgresSchema, + dict_attributes: PythonTypedDictAttr[], + parent_class: string = 'BaseModel' + ) { + this.schema = schema + this.dict_attributes = dict_attributes + this.table_name = name + this.name = `${formatForPyClassName(schema.name)}${formatForPyClassName(name)}` + this.parent_class = parent_class + this.operation = operation + } + serialize(): string { + const attributes = + this.dict_attributes.length > 0 + ? this.dict_attributes.map((attr) => attr.serialize()).join('\n') + : ' pass' + return `class ${this.name}${this.operation}(TypedDict):\n${attributes}` + } +} + +function concatLines(items: Serializable[]): string { + return items.map((item) => item.serialize()).join('\n\n') +} + +const PY_TYPE_MAP: Record = { + // Bool + bool: 'bool', + + // Numbers + int2: 'int', + int4: 'int', + int8: 'int', + float4: 'float', + float8: 'float', + numeric: 'float', + + // Strings + bytea: 'bytes', + bpchar: 'str', + varchar: 'str', + string: 'str', + date: 'datetime.date', + text: 'str', + citext: 'str', + time: 'datetime.time', + timetz: 'datetime.time', + timestamp: 'datetime.datetime', + timestamptz: 'datetime.datetime', + uuid: 'uuid.UUID', + vector: 'list[Any]', + + // JSON + json: 'Json[Any]', + jsonb: 'Json[Any]', + + // Range types (can be adjusted to more complex types if needed) + int4range: 'str', + int4multirange: 'str', + int8range: 'str', + int8multirange: 'str', + numrange: 'str', + nummultirange: 'str', + tsrange: 'str', + tsmultirange: 'str', + tstzrange: 'str', + tstzmultirange: 'str', + daterange: 'str', + datemultirange: 'str', + + // Miscellaneous types + void: 'None', + record: 'dict[str, Any]', +} as const + +/** + * Converts a Postgres name to PascalCase. + * + * @example + * ```ts + * formatForPyTypeName('pokedex') // Pokedex + * formatForPyTypeName('pokemon_center') // PokemonCenter + * formatForPyTypeName('victory-road') // VictoryRoad + * formatForPyTypeName('pokemon league') // PokemonLeague + * ``` + */ + +function formatForPyClassName(name: string): string { + return name + .split(/[^a-zA-Z0-9]/) + .map((word) => { + if (word) { + return `${word[0].toUpperCase()}${word.slice(1)}` + } else { + return '' + } + }) + .join('') +} +/** + * Converts a Postgres name to snake_case. + * + * @example + * ```ts + * formatForPyTypeName('Pokedex') // pokedex + * formatForPyTypeName('PokemonCenter') // pokemon_enter + * formatForPyTypeName('victory-road') // victory_road + * formatForPyTypeName('pokemon league') // pokemon_league + * ``` + */ +function formatForPyAttributeName(name: string): string { + return name + .split(/[^a-zA-Z0-9]+/) // Split on non-alphanumeric characters (like spaces, dashes, etc.) + .map((word) => word.toLowerCase()) // Convert each word to lowercase + .join('_') // Join with underscores +} diff --git a/src/server/templates/swift.ts b/src/server/templates/swift.ts new file mode 100644 index 00000000..7bb41207 --- /dev/null +++ b/src/server/templates/swift.ts @@ -0,0 +1,420 @@ +import prettier from 'prettier' +import type { + PostgresColumn, + PostgresFunction, + PostgresMaterializedView, + PostgresSchema, + PostgresTable, + PostgresType, + PostgresView, +} from '../../lib/index.js' +import type { GeneratorMetadata } from '../../lib/generators.js' +import { PostgresForeignTable } from '../../lib/types.js' + +type Operation = 'Select' | 'Insert' | 'Update' +export type AccessControl = 'internal' | 'public' | 'private' | 'package' + +type SwiftGeneratorOptions = { + accessControl: AccessControl +} + +type SwiftEnumCase = { + formattedName: string + rawValue: string +} + +type SwiftEnum = { + formattedEnumName: string + protocolConformances: string[] + cases: SwiftEnumCase[] +} + +type SwiftAttribute = { + formattedAttributeName: string + formattedType: string + rawName: string + isIdentity: boolean +} + +type SwiftStruct = { + formattedStructName: string + protocolConformances: string[] + attributes: SwiftAttribute[] + codingKeysEnum: SwiftEnum | undefined +} + +function formatForSwiftSchemaName(schema: string): string { + return `${formatForSwiftTypeName(schema)}Schema` +} + +function pgEnumToSwiftEnum(pgEnum: PostgresType): SwiftEnum { + return { + formattedEnumName: formatForSwiftTypeName(pgEnum.name), + protocolConformances: ['String', 'Codable', 'Hashable', 'Sendable'], + cases: pgEnum.enums.map((case_) => { + return { formattedName: formatForSwiftPropertyName(case_), rawValue: case_ } + }), + } +} + +function pgTypeToSwiftStruct( + table: PostgresTable | PostgresForeignTable | PostgresView | PostgresMaterializedView, + columns: PostgresColumn[] | undefined, + operation: Operation, + { + types, + views, + tables, + }: { types: PostgresType[]; views: PostgresView[]; tables: PostgresTable[] } +): SwiftStruct { + const columnEntries: SwiftAttribute[] = + columns?.map((column) => { + let nullable: boolean + + if (operation === 'Insert') { + nullable = + column.is_nullable || column.is_identity || column.is_generated || !!column.default_value + } else if (operation === 'Update') { + nullable = true + } else { + nullable = column.is_nullable + } + + return { + rawName: column.name, + formattedAttributeName: formatForSwiftPropertyName(column.name), + formattedType: pgTypeToSwiftType(column.format, nullable, { types, views, tables }), + isIdentity: column.is_identity, + } + }) ?? [] + + return { + formattedStructName: `${formatForSwiftTypeName(table.name)}${operation}`, + attributes: columnEntries, + protocolConformances: ['Codable', 'Hashable', 'Sendable'], + codingKeysEnum: generateCodingKeysEnumFromAttributes(columnEntries), + } +} + +function generateCodingKeysEnumFromAttributes(attributes: SwiftAttribute[]): SwiftEnum | undefined { + return attributes.length > 0 + ? { + formattedEnumName: 'CodingKeys', + protocolConformances: ['String', 'CodingKey'], + cases: attributes.map((attribute) => { + return { + formattedName: attribute.formattedAttributeName, + rawValue: attribute.rawName, + } + }), + } + : undefined +} + +function pgCompositeTypeToSwiftStruct( + type: PostgresType, + { + types, + views, + tables, + }: { types: PostgresType[]; views: PostgresView[]; tables: PostgresTable[] } +): SwiftStruct { + const typeWithRetrievedAttributes = { + ...type, + attributes: type.attributes.map((attribute) => { + const type = types.find((type) => type.id === attribute.type_id) + return { + ...attribute, + type, + } + }), + } + + const attributeEntries: SwiftAttribute[] = typeWithRetrievedAttributes.attributes.map( + (attribute) => { + return { + formattedAttributeName: formatForSwiftTypeName(attribute.name), + formattedType: pgTypeToSwiftType(attribute.type!.format, false, { types, views, tables }), + rawName: attribute.name, + isIdentity: false, + } + } + ) + + return { + formattedStructName: formatForSwiftTypeName(type.name), + attributes: attributeEntries, + protocolConformances: ['Codable', 'Hashable', 'Sendable'], + codingKeysEnum: generateCodingKeysEnumFromAttributes(attributeEntries), + } +} + +function generateProtocolConformances(protocols: string[]): string { + return protocols.length === 0 ? '' : `: ${protocols.join(', ')}` +} + +function generateEnum( + enum_: SwiftEnum, + { accessControl, level }: SwiftGeneratorOptions & { level: number } +): string[] { + return [ + `${ident(level)}${accessControl} enum ${enum_.formattedEnumName}${generateProtocolConformances(enum_.protocolConformances)} {`, + ...enum_.cases.map( + (case_) => `${ident(level + 1)}case ${case_.formattedName} = "${case_.rawValue}"` + ), + `${ident(level)}}`, + ] +} + +function generateStruct( + struct: SwiftStruct, + { accessControl, level }: SwiftGeneratorOptions & { level: number } +): string[] { + const identity = struct.attributes.find((column) => column.isIdentity) + + let protocolConformances = struct.protocolConformances + if (identity) { + protocolConformances.push('Identifiable') + } + + let output = [ + `${ident(level)}${accessControl} struct ${struct.formattedStructName}${generateProtocolConformances(struct.protocolConformances)} {`, + ] + + if (identity && identity.formattedAttributeName !== 'id') { + output.push( + `${ident(level + 1)}${accessControl} var id: ${identity.formattedType} { ${identity.formattedAttributeName} }` + ) + } + + output.push( + ...struct.attributes.map( + (attribute) => + `${ident(level + 1)}${accessControl} let ${attribute.formattedAttributeName}: ${attribute.formattedType}` + ) + ) + + if (struct.codingKeysEnum) { + output.push(...generateEnum(struct.codingKeysEnum, { accessControl, level: level + 1 })) + } + + output.push(`${ident(level)}}`) + + return output +} + +export const apply = async ({ + schemas, + tables, + foreignTables, + views, + materializedViews, + columns, + types, + accessControl, +}: GeneratorMetadata & SwiftGeneratorOptions): Promise => { + const columnsByTableId = Object.fromEntries( + [...tables, ...foreignTables, ...views, ...materializedViews].map((t) => [t.id, []]) + ) + + columns + .filter((c) => c.table_id in columnsByTableId) + .sort(({ name: a }, { name: b }) => a.localeCompare(b)) + .forEach((c) => columnsByTableId[c.table_id].push(c)) + + let output = [ + 'import Foundation', + 'import Supabase', + '', + ...schemas + .sort(({ name: a }, { name: b }) => a.localeCompare(b)) + .flatMap((schema) => { + const schemaTables = [...tables, ...foreignTables] + .filter((table) => table.schema === schema.name) + .sort(({ name: a }, { name: b }) => a.localeCompare(b)) + + const schemaViews = [...views, ...materializedViews] + .filter((table) => table.schema === schema.name) + .sort(({ name: a }, { name: b }) => a.localeCompare(b)) + + const schemaEnums = types + .filter((type) => type.schema === schema.name && type.enums.length > 0) + .sort(({ name: a }, { name: b }) => a.localeCompare(b)) + + const schemaCompositeTypes = types + .filter((type) => type.schema === schema.name && type.attributes.length > 0) + .sort(({ name: a }, { name: b }) => a.localeCompare(b)) + + return [ + `${accessControl} enum ${formatForSwiftSchemaName(schema.name)} {`, + ...schemaEnums.flatMap((enum_) => + generateEnum(pgEnumToSwiftEnum(enum_), { accessControl, level: 1 }) + ), + ...schemaTables.flatMap((table) => + (['Select', 'Insert', 'Update'] as Operation[]) + .map((operation) => + pgTypeToSwiftStruct(table, columnsByTableId[table.id], operation, { + types, + views, + tables, + }) + ) + .flatMap((struct) => generateStruct(struct, { accessControl, level: 1 })) + ), + ...schemaViews.flatMap((view) => + generateStruct( + pgTypeToSwiftStruct(view, columnsByTableId[view.id], 'Select', { + types, + views, + tables, + }), + { accessControl, level: 1 } + ) + ), + ...schemaCompositeTypes.flatMap((type) => + generateStruct(pgCompositeTypeToSwiftStruct(type, { types, views, tables }), { + accessControl, + level: 1, + }) + ), + '}', + ] + }), + ] + + return output.join('\n') +} + +// TODO: Make this more robust. Currently doesn't handle range types - returns them as string. +const pgTypeToSwiftType = ( + pgType: string, + nullable: boolean, + { + types, + views, + tables, + }: { types: PostgresType[]; views: PostgresView[]; tables: PostgresTable[] } +): string => { + let swiftType: string + + if (pgType === 'bool') { + swiftType = 'Bool' + } else if (pgType === 'int2') { + swiftType = 'Int16' + } else if (pgType === 'int4') { + swiftType = 'Int32' + } else if (pgType === 'int8') { + swiftType = 'Int64' + } else if (pgType === 'float4') { + swiftType = 'Float' + } else if (pgType === 'float8') { + swiftType = 'Double' + } else if (['numeric', 'decimal'].includes(pgType)) { + swiftType = 'Decimal' + } else if (pgType === 'uuid') { + swiftType = 'UUID' + } else if ( + [ + 'bytea', + 'bpchar', + 'varchar', + 'date', + 'text', + 'citext', + 'time', + 'timetz', + 'timestamp', + 'timestamptz', + 'vector', + ].includes(pgType) + ) { + swiftType = 'String' + } else if (['json', 'jsonb'].includes(pgType)) { + swiftType = 'AnyJSON' + } else if (pgType === 'void') { + swiftType = 'Void' + } else if (pgType === 'record') { + swiftType = 'JSONObject' + } else if (pgType.startsWith('_')) { + swiftType = `[${pgTypeToSwiftType(pgType.substring(1), false, { types, views, tables })}]` + } else { + const enumType = types.find((type) => type.name === pgType && type.enums.length > 0) + + const compositeTypes = [...types, ...views, ...tables].find((type) => type.name === pgType) + + if (enumType) { + swiftType = `${formatForSwiftTypeName(enumType.name)}` + } else if (compositeTypes) { + // Append a `Select` to the composite type, as that is how is named in the generated struct. + swiftType = `${formatForSwiftTypeName(compositeTypes.name)}Select` + } else { + swiftType = 'AnyJSON' + } + } + + return `${swiftType}${nullable ? '?' : ''}` +} + +function ident(level: number, options: { width: number } = { width: 2 }): string { + return ' '.repeat(level * options.width) +} + +/** + * Converts a Postgres name to PascalCase. + * + * @example + * ```ts + * formatForSwiftTypeName('pokedex') // Pokedex + * formatForSwiftTypeName('pokemon_center') // PokemonCenter + * formatForSwiftTypeName('victory-road') // VictoryRoad + * formatForSwiftTypeName('pokemon league') // PokemonLeague + * formatForSwiftTypeName('_key_id_context') // _KeyIdContext + * ``` + */ +function formatForSwiftTypeName(name: string): string { + // Preserve the initial underscore if it exists + let prefix = '' + if (name.startsWith('_')) { + prefix = '_' + name = name.slice(1) // Remove the initial underscore for processing + } + + return ( + prefix + + name + .split(/[^a-zA-Z0-9]+/) + .map((word) => { + if (word) { + return `${word[0].toUpperCase()}${word.slice(1)}` + } else { + return '' + } + }) + .join('') + ) +} + +const SWIFT_KEYWORDS = ['in', 'default', 'case'] + +/** + * Converts a Postgres name to pascalCase. + * + * @example + * ```ts + * formatForSwiftTypeName('pokedex') // pokedex + * formatForSwiftTypeName('pokemon_center') // pokemonCenter + * formatForSwiftTypeName('victory-road') // victoryRoad + * formatForSwiftTypeName('pokemon league') // pokemonLeague + * ``` + */ +function formatForSwiftPropertyName(name: string): string { + const propertyName = name + .split(/[^a-zA-Z0-9]/) + .map((word, index) => { + const lowerWord = word.toLowerCase() + return index !== 0 ? lowerWord.charAt(0).toUpperCase() + lowerWord.slice(1) : lowerWord + }) + .join('') + + return SWIFT_KEYWORDS.includes(propertyName) ? `\`${propertyName}\`` : propertyName +} diff --git a/src/server/templates/typescript.ts b/src/server/templates/typescript.ts new file mode 100644 index 00000000..75ca7de4 --- /dev/null +++ b/src/server/templates/typescript.ts @@ -0,0 +1,973 @@ +import prettier from 'prettier' +import type { GeneratorMetadata } from '../../lib/generators.js' +import type { + PostgresColumn, + PostgresFunction, + PostgresSchema, + PostgresTable, + PostgresType, + PostgresView, +} from '../../lib/index.js' +import { + GENERATE_TYPES_DEFAULT_SCHEMA, + VALID_FUNCTION_ARGS_MODE, + VALID_UNNAMED_FUNCTION_ARG_TYPES, +} from '../constants.js' + +type TsRelationship = Pick< + GeneratorMetadata['relationships'][number], + 'foreign_key_name' | 'columns' | 'is_one_to_one' | 'referenced_relation' | 'referenced_columns' +> + +export const apply = async ({ + schemas, + tables, + foreignTables, + views, + materializedViews, + columns, + relationships, + functions, + types, + detectOneToOneRelationships, + postgrestVersion, +}: GeneratorMetadata & { + detectOneToOneRelationships: boolean + postgrestVersion?: string +}): Promise => { + schemas.sort((a, b) => a.name.localeCompare(b.name)) + relationships.sort( + (a, b) => + a.foreign_key_name.localeCompare(b.foreign_key_name) || + a.referenced_relation.localeCompare(b.referenced_relation) || + JSON.stringify(a.referenced_columns).localeCompare(JSON.stringify(b.referenced_columns)) + ) + const introspectionBySchema = Object.fromEntries<{ + tables: { + table: Pick + relationships: TsRelationship[] + }[] + views: { + view: PostgresView + relationships: TsRelationship[] + }[] + functions: { fn: PostgresFunction; inArgs: PostgresFunction['args'] }[] + enums: PostgresType[] + compositeTypes: PostgresType[] + }>( + schemas.map((s) => [ + s.name, + { tables: [], views: [], functions: [], enums: [], compositeTypes: [] }, + ]) + ) + const columnsByTableId: Record = {} + const tablesNamesByTableId: Record = {} + const relationTypeByIds = new Map() + // group types by id for quicker lookup + const typesById = new Map() + const tablesLike = [...tables, ...foreignTables, ...views, ...materializedViews] + + for (const tableLike of tablesLike) { + columnsByTableId[tableLike.id] = [] + tablesNamesByTableId[tableLike.id] = tableLike.name + } + for (const column of columns) { + if (column.table_id in columnsByTableId) { + columnsByTableId[column.table_id].push(column) + } + } + for (const tableId in columnsByTableId) { + columnsByTableId[tableId].sort((a, b) => a.name.localeCompare(b.name)) + } + + for (const type of types) { + typesById.set(type.id, type) + // Save all the types that are relation types for quicker lookup + if (type.type_relation_id) { + relationTypeByIds.set(type.id, type) + } + if (type.schema in introspectionBySchema) { + if (type.enums.length > 0) { + introspectionBySchema[type.schema].enums.push(type) + } + if (type.attributes.length > 0) { + introspectionBySchema[type.schema].compositeTypes.push(type) + } + } + } + + function getRelationships( + object: { schema: string; name: string }, + relationships: GeneratorMetadata['relationships'] + ): Pick< + GeneratorMetadata['relationships'][number], + 'foreign_key_name' | 'columns' | 'is_one_to_one' | 'referenced_relation' | 'referenced_columns' + >[] { + return relationships.filter( + (relationship) => + relationship.schema === object.schema && + relationship.referenced_schema === object.schema && + relationship.relation === object.name + ) + } + + function generateRelationshiptTsDefinition(relationship: TsRelationship): string { + return `{ + foreignKeyName: ${JSON.stringify(relationship.foreign_key_name)} + columns: ${JSON.stringify(relationship.columns)}${detectOneToOneRelationships ? `\nisOneToOne: ${relationship.is_one_to_one}` : ''} + referencedRelation: ${JSON.stringify(relationship.referenced_relation)} + referencedColumns: ${JSON.stringify(relationship.referenced_columns)} + }` + } + + for (const table of tables) { + if (table.schema in introspectionBySchema) { + introspectionBySchema[table.schema].tables.push({ + table, + relationships: getRelationships(table, relationships), + }) + } + } + for (const table of foreignTables) { + if (table.schema in introspectionBySchema) { + introspectionBySchema[table.schema].tables.push({ + table, + relationships: getRelationships(table, relationships), + }) + } + } + for (const view of views) { + if (view.schema in introspectionBySchema) { + introspectionBySchema[view.schema].views.push({ + view, + relationships: getRelationships(view, relationships), + }) + } + } + for (const materializedView of materializedViews) { + if (materializedView.schema in introspectionBySchema) { + introspectionBySchema[materializedView.schema].views.push({ + view: { + ...materializedView, + is_updatable: false, + }, + relationships: getRelationships(materializedView, relationships), + }) + } + } + // Helper function to get table/view name from relation id + const getTableNameFromRelationId = ( + relationId: number | null, + returnTypeId: number | null + ): string | null => { + if (!relationId) return null + + if (tablesNamesByTableId[relationId]) return tablesNamesByTableId[relationId] + // if it's a composite type we use the type name as relation name to allow sub-selecting fields of the composite type + const reltype = returnTypeId ? relationTypeByIds.get(returnTypeId) : null + return reltype ? reltype.name : null + } + + for (const func of functions) { + if (func.schema in introspectionBySchema) { + func.args.sort((a, b) => a.name.localeCompare(b.name)) + // Get all input args (in, inout, variadic modes) + const inArgs = func.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode)) + + if ( + // Case 1: Function has no parameters + inArgs.length === 0 || + // Case 2: All input args are named + !inArgs.some(({ name }) => name === '') || + // Case 3: All unnamed args have default values AND are valid types + inArgs.every((arg) => { + if (arg.name === '') { + return arg.has_default && VALID_UNNAMED_FUNCTION_ARG_TYPES.has(arg.type_id) + } + return true + }) || + // Case 4: Single unnamed parameter of valid type (json, jsonb, text) + // Exclude all functions definitions that have only one single argument unnamed argument that isn't + // a json/jsonb/text as it won't be considered by PostgREST + (inArgs.length === 1 && + inArgs[0].name === '' && + (VALID_UNNAMED_FUNCTION_ARG_TYPES.has(inArgs[0].type_id) || + // OR if the function have a single unnamed args which is another table (embeded function) + (relationTypeByIds.get(inArgs[0].type_id) && + getTableNameFromRelationId(func.return_type_relation_id, func.return_type_id)) || + // OR if the function takes a table row but doesn't qualify as embedded (for error reporting) + (relationTypeByIds.get(inArgs[0].type_id) && + !getTableNameFromRelationId(func.return_type_relation_id, func.return_type_id)))) + ) { + introspectionBySchema[func.schema].functions.push({ fn: func, inArgs }) + } + } + } + for (const schema in introspectionBySchema) { + introspectionBySchema[schema].tables.sort((a, b) => a.table.name.localeCompare(b.table.name)) + introspectionBySchema[schema].views.sort((a, b) => a.view.name.localeCompare(b.view.name)) + introspectionBySchema[schema].functions.sort((a, b) => a.fn.name.localeCompare(b.fn.name)) + introspectionBySchema[schema].enums.sort((a, b) => a.name.localeCompare(b.name)) + introspectionBySchema[schema].compositeTypes.sort((a, b) => a.name.localeCompare(b.name)) + } + + const getFunctionTsReturnType = (fn: PostgresFunction, returnType: string) => { + // Determine if this function should have SetofOptions + let setofOptionsInfo = '' + + const returnTableName = getTableNameFromRelationId( + fn.return_type_relation_id, + fn.return_type_id + ) + const returnsSetOfTable = fn.is_set_returning_function && fn.return_type_relation_id !== null + const returnsMultipleRows = fn.prorows !== null && fn.prorows > 1 + // Case 1: if the function returns a table, we need to add SetofOptions to allow selecting sub fields of the table + // Those can be used in rpc to select sub fields of a table + if (returnTableName) { + setofOptionsInfo = `SetofOptions: { + from: "*" + to: ${JSON.stringify(returnTableName)} + isOneToOne: ${Boolean(!returnsMultipleRows)} + isSetofReturn: ${fn.is_set_returning_function} + }` + } + // Case 2: if the function has a single table argument, we need to add SetofOptions to allow selecting sub fields of the table + // and set the right "from" and "to" values to allow selecting from a table row + if (fn.args.length === 1) { + const relationType = relationTypeByIds.get(fn.args[0].type_id) + + // Only add SetofOptions for functions with table arguments (embedded functions) + // or specific functions that RETURNS table-name + if (relationType) { + const sourceTable = relationType.format + // Case 1: Standard embedded function with proper setof detection + if (returnsSetOfTable && returnTableName) { + setofOptionsInfo = `SetofOptions: { + from: ${JSON.stringify(sourceTable)} + to: ${JSON.stringify(returnTableName)} + isOneToOne: ${Boolean(!returnsMultipleRows)} + isSetofReturn: true + }` + } + // Case 2: Handle RETURNS table-name those are always a one to one relationship + else if (returnTableName && !returnsSetOfTable) { + const targetTable = returnTableName + setofOptionsInfo = `SetofOptions: { + from: ${JSON.stringify(sourceTable)} + to: ${JSON.stringify(targetTable)} + isOneToOne: true + isSetofReturn: false + }` + } + } + } + + return `${returnType}${fn.is_set_returning_function && returnsMultipleRows ? '[]' : ''} + ${setofOptionsInfo ? `${setofOptionsInfo}` : ''}` + } + + const getFunctionReturnType = (schema: PostgresSchema, fn: PostgresFunction): string => { + // Case 1: `returns table`. + const tableArgs = fn.args.filter(({ mode }) => mode === 'table') + if (tableArgs.length > 0) { + const argsNameAndType = tableArgs.map(({ name, type_id }) => { + const type = typesById.get(type_id) + let tsType = 'unknown' + if (type) { + tsType = pgTypeToTsType(schema, type.name, { + types, + schemas, + tables, + views, + }) + } + return { name, type: tsType } + }) + + return `{ + ${argsNameAndType.map(({ name, type }) => `${JSON.stringify(name)}: ${type}`)} + }` + } + + // Case 2: returns a relation's row type. + const relation = + introspectionBySchema[schema.name]?.tables.find( + ({ table: { id } }) => id === fn.return_type_relation_id + )?.table || + introspectionBySchema[schema.name]?.views.find( + ({ view: { id } }) => id === fn.return_type_relation_id + )?.view + if (relation) { + return `{ + ${columnsByTableId[relation.id] + .map((column) => + generateColumnTsDefinition( + schema, + { + name: column.name, + format: column.format, + is_nullable: column.is_nullable, + is_optional: false, + }, + { + types, + schemas, + tables, + views, + } + ) + ) + .join(',\n')} + }` + } + + // Case 3: returns base/array/composite/enum type. + const type = typesById.get(fn.return_type_id) + if (type) { + return pgTypeToTsType(schema, type.name, { + types, + schemas, + tables, + views, + }) + } + + return 'unknown' + } + // Special error case for functions that take table row but don't qualify as embedded functions + const hasTableRowError = (fn: PostgresFunction, inArgs: PostgresFunction['args']) => { + if ( + inArgs.length === 1 && + inArgs[0].name === '' && + relationTypeByIds.get(inArgs[0].type_id) && + !getTableNameFromRelationId(fn.return_type_relation_id, fn.return_type_id) + ) { + return true + } + return false + } + + // Check for generic conflict cases that need error reporting + const getConflictError = ( + schema: PostgresSchema, + fns: Array<{ fn: PostgresFunction; inArgs: PostgresFunction['args'] }>, + fn: PostgresFunction, + inArgs: PostgresFunction['args'] + ) => { + // If there is a single function definition, there is no conflict + if (fns.length <= 1) return null + + // Generic conflict detection patterns + // Pattern 1: No-args vs default-args conflicts + if (inArgs.length === 0) { + const conflictingFns = fns.filter(({ fn: otherFn, inArgs: otherInArgs }) => { + if (otherFn === fn) return false + return otherInArgs.length === 1 && otherInArgs[0].name === '' && otherInArgs[0].has_default + }) + + if (conflictingFns.length > 0) { + const conflictingFn = conflictingFns[0] + const returnTypeName = typesById.get(conflictingFn.fn.return_type_id)?.name || 'unknown' + return `Could not choose the best candidate function between: ${schema.name}.${fn.name}(), ${schema.name}.${fn.name}( => ${returnTypeName}). Try renaming the parameters or the function itself in the database so function overloading can be resolved` + } + } + + // Pattern 2: Same parameter name but different types (unresolvable overloads) + if (inArgs.length === 1 && inArgs[0].name !== '') { + const conflictingFns = fns.filter(({ fn: otherFn, inArgs: otherInArgs }) => { + if (otherFn === fn) return false + return ( + otherInArgs.length === 1 && + otherInArgs[0].name === inArgs[0].name && + otherInArgs[0].type_id !== inArgs[0].type_id + ) + }) + + if (conflictingFns.length > 0) { + const allConflictingFunctions = [{ fn, inArgs }, ...conflictingFns] + const conflictList = allConflictingFunctions + .sort((a, b) => { + const aArgs = a.inArgs + const bArgs = b.inArgs + return (aArgs[0]?.type_id || 0) - (bArgs[0]?.type_id || 0) + }) + .map((f) => { + const args = f.inArgs + return `${schema.name}.${fn.name}(${args.map((a) => `${a.name || ''} => ${typesById.get(a.type_id)?.name || 'unknown'}`).join(', ')})` + }) + .join(', ') + + return `Could not choose the best candidate function between: ${conflictList}. Try renaming the parameters or the function itself in the database so function overloading can be resolved` + } + } + + return null + } + + const getFunctionSignatures = ( + schema: PostgresSchema, + fns: Array<{ fn: PostgresFunction; inArgs: PostgresFunction['args'] }> + ) => { + return fns + .map(({ fn, inArgs }) => { + let argsType = 'never' + let returnType = getFunctionReturnType(schema, fn) + + // Check for specific error cases + const conflictError = getConflictError(schema, fns, fn, inArgs) + if (conflictError) { + if (inArgs.length > 0) { + const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { + const type = typesById.get(type_id) + let tsType = 'unknown' + if (type) { + tsType = pgTypeToTsType(schema, type.name, { + types, + schemas, + tables, + views, + }) + } + return { name, type: tsType, has_default } + }) + argsType = `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` + } + returnType = `{ error: true } & ${JSON.stringify(conflictError)}` + } else if (hasTableRowError(fn, inArgs)) { + // Special case for computed fields returning scalars functions + if (inArgs.length > 0) { + const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { + const type = typesById.get(type_id) + let tsType = 'unknown' + if (type) { + tsType = pgTypeToTsType(schema, type.name, { + types, + schemas, + tables, + views, + }) + } + return { name, type: tsType, has_default } + }) + argsType = `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` + } + returnType = `{ error: true } & ${JSON.stringify(`the function ${schema.name}.${fn.name} with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache`)}` + } else if (inArgs.length > 0) { + const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { + const type = typesById.get(type_id) + let tsType = 'unknown' + if (type) { + tsType = pgTypeToTsType(schema, type.name, { + types, + schemas, + tables, + views, + }) + } + return { name, type: tsType, has_default } + }) + argsType = `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` + } + + return `{ Args: ${argsType}; Returns: ${getFunctionTsReturnType(fn, returnType)} }` + }) + .join(' |\n') + } + + const internal_supabase_schema = postgrestVersion + ? `// Allows to automatically instantiate createClient with right options + // instead of createClient(URL, KEY) + __InternalSupabase: { + PostgrestVersion: '${postgrestVersion}' + }` + : '' + + function generateNullableUnionTsType(tsType: string, isNullable: boolean) { + // Only add the null union if the type is not unknown as unknown already includes null + if (tsType === 'unknown' || tsType === 'any' || !isNullable) { + return tsType + } + return `${tsType} | null` + } + + function generateColumnTsDefinition( + schema: PostgresSchema, + column: { + name: string + format: string + is_nullable: boolean + is_optional: boolean + }, + context: { + types: PostgresType[] + schemas: PostgresSchema[] + tables: PostgresTable[] + views: PostgresView[] + } + ) { + return `${JSON.stringify(column.name)}${column.is_optional ? '?' : ''}: ${generateNullableUnionTsType(pgTypeToTsType(schema, column.format, context), column.is_nullable)}` + } + + let output = ` +export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[] + +export type Database = { + ${internal_supabase_schema} + ${schemas.map((schema) => { + const { + tables: schemaTables, + views: schemaViews, + functions: schemaFunctions, + enums: schemaEnums, + compositeTypes: schemaCompositeTypes, + } = introspectionBySchema[schema.name] + return `${JSON.stringify(schema.name)}: { + Tables: { + ${ + schemaTables.length === 0 + ? '[_ in never]: never' + : schemaTables.map( + ({ table, relationships }) => `${JSON.stringify(table.name)}: { + Row: { + ${[ + ...columnsByTableId[table.id].map((column) => + generateColumnTsDefinition( + schema, + { + name: column.name, + format: column.format, + is_nullable: column.is_nullable, + is_optional: false, + }, + { types, schemas, tables, views } + ) + ), + ...schemaFunctions + .filter(({ fn }) => fn.argument_types === table.name) + .map(({ fn }) => { + return `${JSON.stringify(fn.name)}: ${generateNullableUnionTsType(getFunctionReturnType(schema, fn), true)}` + }), + ]} + } + Insert: { + ${columnsByTableId[table.id].map((column) => { + if (column.identity_generation === 'ALWAYS') { + return `${JSON.stringify(column.name)}?: never` + } + return generateColumnTsDefinition( + schema, + { + name: column.name, + format: column.format, + is_nullable: column.is_nullable, + is_optional: + column.is_nullable || + column.is_identity || + column.default_value !== null, + }, + { types, schemas, tables, views } + ) + })} + } + Update: { + ${columnsByTableId[table.id].map((column) => { + if (column.identity_generation === 'ALWAYS') { + return `${JSON.stringify(column.name)}?: never` + } + + return generateColumnTsDefinition( + schema, + { + name: column.name, + format: column.format, + is_nullable: column.is_nullable, + is_optional: true, + }, + { types, schemas, tables, views } + ) + })} + } + Relationships: [ + ${relationships.map(generateRelationshiptTsDefinition)} + ] + }` + ) + } + } + Views: { + ${ + schemaViews.length === 0 + ? '[_ in never]: never' + : schemaViews.map( + ({ view, relationships }) => `${JSON.stringify(view.name)}: { + Row: { + ${[ + ...columnsByTableId[view.id].map((column) => + generateColumnTsDefinition( + schema, + { + name: column.name, + format: column.format, + is_nullable: column.is_nullable, + is_optional: false, + }, + { types, schemas, tables, views } + ) + ), + ...schemaFunctions + .filter(({ fn }) => fn.argument_types === view.name) + .map( + ({ fn }) => + `${JSON.stringify(fn.name)}: ${generateNullableUnionTsType(getFunctionReturnType(schema, fn), true)}` + ), + ]} + } + ${ + view.is_updatable + ? `Insert: { + ${columnsByTableId[view.id].map((column) => { + if (!column.is_updatable) { + return `${JSON.stringify(column.name)}?: never` + } + return generateColumnTsDefinition( + schema, + { + name: column.name, + format: column.format, + is_nullable: true, + is_optional: true, + }, + { types, schemas, tables, views } + ) + })} + } + Update: { + ${columnsByTableId[view.id].map((column) => { + if (!column.is_updatable) { + return `${JSON.stringify(column.name)}?: never` + } + return generateColumnTsDefinition( + schema, + { + name: column.name, + format: column.format, + is_nullable: true, + is_optional: true, + }, + { types, schemas, tables, views } + ) + })} + } + ` + : '' + }Relationships: [ + ${relationships.map(generateRelationshiptTsDefinition)} + ] + }` + ) + } + } + Functions: { + ${(() => { + if (schemaFunctions.length === 0) { + return '[_ in never]: never' + } + const schemaFunctionsGroupedByName = schemaFunctions.reduce( + (acc, curr) => { + acc[curr.fn.name] ??= [] + acc[curr.fn.name].push(curr) + return acc + }, + {} as Record + ) + for (const fnName in schemaFunctionsGroupedByName) { + schemaFunctionsGroupedByName[fnName].sort( + (a, b) => + a.fn.argument_types.localeCompare(b.fn.argument_types) || + a.fn.return_type.localeCompare(b.fn.return_type) + ) + } + + return Object.entries(schemaFunctionsGroupedByName) + .map(([fnName, fns]) => { + const functionSignatures = getFunctionSignatures(schema, fns) + return `${JSON.stringify(fnName)}:\n${functionSignatures}` + }) + .join(',\n') + })()} + } + Enums: { + ${ + schemaEnums.length === 0 + ? '[_ in never]: never' + : schemaEnums.map( + (enum_) => + `${JSON.stringify(enum_.name)}: ${enum_.enums + .map((variant) => JSON.stringify(variant)) + .join('|')}` + ) + } + } + CompositeTypes: { + ${ + schemaCompositeTypes.length === 0 + ? '[_ in never]: never' + : schemaCompositeTypes.map( + ({ name, attributes }) => + `${JSON.stringify(name)}: { + ${attributes.map(({ name, type_id }) => { + const type = typesById.get(type_id) + let tsType = 'unknown' + if (type) { + tsType = `${generateNullableUnionTsType( + pgTypeToTsType(schema, type.name, { + types, + schemas, + tables, + views, + }), + true + )}` + } + return `${JSON.stringify(name)}: ${tsType}` + })} + }` + ) + } + } + }` + })} +} + +type DatabaseWithoutInternals = Omit + +type DefaultSchema = DatabaseWithoutInternals[Extract] + +export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never +> = DefaultSchemaTableNameOrOptions extends { schema: keyof DatabaseWithoutInternals } + ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never +> = DefaultSchemaTableNameOrOptions extends { schema: keyof DatabaseWithoutInternals } + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never +> = DefaultSchemaTableNameOrOptions extends { schema: keyof DatabaseWithoutInternals } + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof DatabaseWithoutInternals }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never +> = DefaultSchemaEnumNameOrOptions extends { schema: keyof DatabaseWithoutInternals } + ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof DatabaseWithoutInternals }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never +> = PublicCompositeTypeNameOrOptions extends { schema: keyof DatabaseWithoutInternals } + ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never + +export const Constants = { + ${schemas.map((schema) => { + const schemaEnums = introspectionBySchema[schema.name].enums + return `${JSON.stringify(schema.name)}: { + Enums: { + ${schemaEnums.map( + (enum_) => + `${JSON.stringify(enum_.name)}: [${enum_.enums + .map((variant) => JSON.stringify(variant)) + .join(', ')}]` + )} + } + }` + })} +} as const +` + + output = await prettier.format(output, { + parser: 'typescript', + semi: false, + }) + return output +} + +// TODO: Make this more robust. Currently doesn't handle range types - returns them as unknown. +const pgTypeToTsType = ( + schema: PostgresSchema, + pgType: string, + { + types, + schemas, + tables, + views, + }: { + types: PostgresType[] + schemas: PostgresSchema[] + tables: PostgresTable[] + views: PostgresView[] + } +): string => { + if (pgType === 'bool') { + return 'boolean' + } else if (['int2', 'int4', 'int8', 'float4', 'float8', 'numeric'].includes(pgType)) { + return 'number' + } else if ( + [ + 'bytea', + 'bpchar', + 'varchar', + 'date', + 'text', + 'citext', + 'time', + 'timetz', + 'timestamp', + 'timestamptz', + 'uuid', + 'vector', + ].includes(pgType) + ) { + return 'string' + } else if (['json', 'jsonb'].includes(pgType)) { + return 'Json' + } else if (pgType === 'void') { + return 'undefined' + } else if (pgType === 'record') { + return 'Record' + } else if (pgType.startsWith('_')) { + return `(${pgTypeToTsType(schema, pgType.substring(1), { + types, + schemas, + tables, + views, + })})[]` + } else { + const enumTypes = types.filter((type) => type.name === pgType && type.enums.length > 0) + if (enumTypes.length > 0) { + const enumType = enumTypes.find((type) => type.schema === schema.name) || enumTypes[0] + if (schemas.some(({ name }) => name === enumType.schema)) { + return `Database[${JSON.stringify(enumType.schema)}]['Enums'][${JSON.stringify( + enumType.name + )}]` + } + return enumType.enums.map((variant) => JSON.stringify(variant)).join('|') + } + + const compositeTypes = types.filter( + (type) => type.name === pgType && type.attributes.length > 0 + ) + if (compositeTypes.length > 0) { + const compositeType = + compositeTypes.find((type) => type.schema === schema.name) || compositeTypes[0] + if (schemas.some(({ name }) => name === compositeType.schema)) { + return `Database[${JSON.stringify( + compositeType.schema + )}]['CompositeTypes'][${JSON.stringify(compositeType.name)}]` + } + return 'unknown' + } + + const tableRowTypes = tables.filter((table) => table.name === pgType) + if (tableRowTypes.length > 0) { + const tableRowType = + tableRowTypes.find((type) => type.schema === schema.name) || tableRowTypes[0] + if (schemas.some(({ name }) => name === tableRowType.schema)) { + return `Database[${JSON.stringify(tableRowType.schema)}]['Tables'][${JSON.stringify( + tableRowType.name + )}]['Row']` + } + return 'unknown' + } + + const viewRowTypes = views.filter((view) => view.name === pgType) + if (viewRowTypes.length > 0) { + const viewRowType = + viewRowTypes.find((type) => type.schema === schema.name) || viewRowTypes[0] + if (schemas.some(({ name }) => name === viewRowType.schema)) { + return `Database[${JSON.stringify(viewRowType.schema)}]['Views'][${JSON.stringify( + viewRowType.name + )}]['Row']` + } + return 'unknown' + } + + return 'unknown' + } +} diff --git a/src/server/utils.ts b/src/server/utils.ts new file mode 100644 index 00000000..ebb8ec90 --- /dev/null +++ b/src/server/utils.ts @@ -0,0 +1,50 @@ +import pgcs from 'pg-connection-string' +import { FastifyRequest } from 'fastify' +import { DEFAULT_POOL_CONFIG } from './constants.js' +import { PoolConfig } from '../lib/types.js' + +export const extractRequestForLogging = (request: FastifyRequest) => { + let pg: string = 'unknown' + try { + if (request.headers.pg) { + pg = pgcs.parse(request.headers.pg as string).host || pg + } + } catch (e: any) { + console.warn('failed to parse PG connstring for ' + request.url) + } + + const additional = request.headers['x-supabase-info']?.toString() || '' + + return { + method: request.method, + url: request.url, + pg, + opt: additional, + } +} + +export function createConnectionConfig(request: FastifyRequest): PoolConfig { + const connectionString = request.headers.pg as string + const config = { ...DEFAULT_POOL_CONFIG, connectionString } + + // Override application_name if custom one provided in header + if (request.headers['x-pg-application-name']) { + config.application_name = request.headers['x-pg-application-name'] as string + } + + return config +} + +export function translateErrorToResponseCode( + error: { message: string }, + defaultResponseCode = 400 +): number { + if (error.message === 'Connection terminated due to connection timeout') { + return 504 + } else if (error.message === 'sorry, too many clients already') { + return 503 + } else if (error.message === 'Query read timeout') { + return 408 + } + return defaultResponseCode +} diff --git a/test/admin-app.test.ts b/test/admin-app.test.ts new file mode 100644 index 00000000..0bc93e2e --- /dev/null +++ b/test/admin-app.test.ts @@ -0,0 +1,17 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/admin-app.js' + +describe('admin-app', () => { + test('should register metrics endpoint', async () => { + const app = build() + + // Test that the app can be started (this will trigger plugin registration) + await app.ready() + + // Verify that metrics endpoint is available + const routes = app.printRoutes() + expect(routes).toContain('metrics') + + await app.close() + }) +}) diff --git a/test/app.test.ts b/test/app.test.ts new file mode 100644 index 00000000..c705dd9b --- /dev/null +++ b/test/app.test.ts @@ -0,0 +1,31 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' + +describe('server/app', () => { + test('should handle root endpoint', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/', + }) + expect(response.statusCode).toBe(200) + const data = JSON.parse(response.body) + expect(data).toHaveProperty('status') + expect(data).toHaveProperty('name') + expect(data).toHaveProperty('version') + expect(data).toHaveProperty('documentation') + await app.close() + }) + + test('should handle health endpoint', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/health', + }) + expect(response.statusCode).toBe(200) + const data = JSON.parse(response.body) + expect(data).toHaveProperty('date') + await app.close() + }) +}) diff --git a/test/config.test.ts b/test/config.test.ts new file mode 100644 index 00000000..2736eb57 --- /dev/null +++ b/test/config.test.ts @@ -0,0 +1,127 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/config', () => { + test('should list config with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/config?limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchInlineSnapshot(` + [ + { + "boot_val": "on", + "category": "Autovacuum", + "context": "sighup", + "enumvals": null, + "extra_desc": null, + "group": "Autovacuum", + "max_val": null, + "min_val": null, + "name": "autovacuum", + "pending_restart": false, + "reset_val": "on", + "setting": "on", + "short_desc": "Starts the autovacuum subprocess.", + "source": "default", + "sourcefile": null, + "sourceline": null, + "subgroup": "", + "unit": null, + "vartype": "bool", + }, + { + "boot_val": "0.1", + "category": "Autovacuum", + "context": "sighup", + "enumvals": null, + "extra_desc": null, + "group": "Autovacuum", + "max_val": "100", + "min_val": "0", + "name": "autovacuum_analyze_scale_factor", + "pending_restart": false, + "reset_val": "0.1", + "setting": "0.1", + "short_desc": "Number of tuple inserts, updates, or deletes prior to analyze as a fraction of reltuples.", + "source": "default", + "sourcefile": null, + "sourceline": null, + "subgroup": "", + "unit": null, + "vartype": "real", + }, + { + "boot_val": "50", + "category": "Autovacuum", + "context": "sighup", + "enumvals": null, + "extra_desc": null, + "group": "Autovacuum", + "max_val": "2147483647", + "min_val": "0", + "name": "autovacuum_analyze_threshold", + "pending_restart": false, + "reset_val": "50", + "setting": "50", + "short_desc": "Minimum number of tuple inserts, updates, or deletes prior to analyze.", + "source": "default", + "sourcefile": null, + "sourceline": null, + "subgroup": "", + "unit": null, + "vartype": "integer", + }, + { + "boot_val": "200000000", + "category": "Autovacuum", + "context": "postmaster", + "enumvals": null, + "extra_desc": null, + "group": "Autovacuum", + "max_val": "2000000000", + "min_val": "100000", + "name": "autovacuum_freeze_max_age", + "pending_restart": false, + "reset_val": "200000000", + "setting": "200000000", + "short_desc": "Age at which to autovacuum a table to prevent transaction ID wraparound.", + "source": "default", + "sourcefile": null, + "sourceline": null, + "subgroup": "", + "unit": null, + "vartype": "integer", + }, + { + "boot_val": "3", + "category": "Autovacuum", + "context": "postmaster", + "enumvals": null, + "extra_desc": null, + "group": "Autovacuum", + "max_val": "262143", + "min_val": "1", + "name": "autovacuum_max_workers", + "pending_restart": false, + "reset_val": "3", + "setting": "3", + "short_desc": "Sets the maximum number of simultaneously running autovacuum worker processes.", + "source": "default", + "sourcefile": null, + "sourceline": null, + "subgroup": "", + "unit": null, + "vartype": "integer", + }, + ] + `) + await app.close() + }) +}) diff --git a/test/db/00-init.sql b/test/db/00-init.sql new file mode 100644 index 00000000..8ddc77ba --- /dev/null +++ b/test/db/00-init.sql @@ -0,0 +1,468 @@ + + +-- Tables for testing + +CREATE TYPE public.user_status AS ENUM ('ACTIVE', 'INACTIVE'); +CREATE TYPE composite_type_with_array_attribute AS (my_text_array text[]); + +CREATE TABLE public.users ( + id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name text, + status user_status DEFAULT 'ACTIVE', + decimal numeric, + user_uuid uuid DEFAULT gen_random_uuid() +); +INSERT INTO + public.users (name) +VALUES + ('Joe Bloggs'), + ('Jane Doe'); + +CREATE TABLE public.todos ( + id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + details text, + "user-id" bigint REFERENCES users NOT NULL +); + +INSERT INTO + public.todos (details, "user-id") +VALUES + ('Star the repo', 1), + ('Watch the releases', 2); + + +CREATE FUNCTION add(integer, integer) RETURNS integer + AS 'select $1 + $2;' + LANGUAGE SQL + IMMUTABLE + RETURNS NULL ON NULL INPUT; + +create table public.users_audit ( + id BIGINT generated by DEFAULT as identity, + created_at timestamptz DEFAULT now(), + user_id bigint, + previous_value jsonb +); + +create function public.audit_action() +returns trigger as $$ +begin + insert into public.users_audit (user_id, previous_value) + values (old.id, row_to_json(old)); + + return new; +end; +$$ language plpgsql; + +CREATE VIEW todos_view AS SELECT * FROM public.todos; +-- For testing typegen on view-to-view relationships +create view users_view as select * from public.users; +-- Create a more complex view for testing +CREATE VIEW user_todos_summary_view AS +SELECT + u.id as user_id, + u.name as user_name, + u.status as user_status, + COUNT(t.id) as todo_count, + array_agg(t.details) FILTER (WHERE t.details IS NOT NULL) as todo_details +FROM public.users u +LEFT JOIN public.todos t ON t."user-id" = u.id +GROUP BY u.id, u.name, u.status; + +create materialized view todos_matview as select * from public.todos; + +create function public.blurb(public.todos) returns text as +$$ +select substring($1.details, 1, 3); +$$ language sql stable; + +create function public.blurb_varchar(public.todos) returns character varying as +$$ +select substring($1.details, 1, 3); +$$ language sql stable; + +create function public.blurb_varchar(public.todos_view) returns character varying as +$$ +select substring($1.details, 1, 3); +$$ language sql stable; + +create function public.details_length(public.todos) returns integer as +$$ +select length($1.details); +$$ language sql stable; + +create function public.details_is_long(public.todos) returns boolean as +$$ +select $1.details_length > 20; +$$ language sql stable; + +create function public.details_words(public.todos) returns text[] as +$$ +select string_to_array($1.details, ' '); +$$ language sql stable; + +create extension postgres_fdw; +create server foreign_server foreign data wrapper postgres_fdw options (host 'localhost', port '5432', dbname 'postgres'); +create user mapping for postgres server foreign_server options (user 'postgres', password 'postgres'); +create foreign table foreign_table ( + id int8 not null, + name text, + status user_status +) server foreign_server options (schema_name 'public', table_name 'users'); + +create or replace function public.function_returning_row() +returns public.users +language sql +stable +as $$ + select * from public.users limit 1; +$$; + +create or replace function public.function_returning_single_row(todos public.todos) +returns public.users +language sql +stable +as $$ + select * from public.users limit 1; +$$; + + +create or replace function public.function_returning_set_of_rows() +returns setof public.users +language sql +stable +as $$ + select * from public.users; +$$; + +create or replace function public.function_returning_table() +returns table (id int, name text) +language sql +stable +as $$ + select id, name from public.users; +$$; + +create or replace function public.function_returning_table_with_args(user_id int) +returns table (id int, name text) +language sql +stable +as $$ + select id, name from public.users WHERE id = user_id; +$$; + + +create or replace function public.polymorphic_function(text) returns void language sql as ''; +create or replace function public.polymorphic_function(bool) returns void language sql as ''; + +create table user_details ( + user_id int8 references users(id) primary key, + details text +); + +create view a_view as select id from users; + +create table empty(); + +create table table_with_other_tables_row_type ( + col1 user_details, + col2 a_view +); + +create table table_with_primary_key_other_than_id ( + other_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name text +); + +create type composite_type_with_record_attribute as ( + todo todos +); + +create view users_view_with_multiple_refs_to_users as +WITH initial_user AS ( + SELECT + u.id as initial_id, + u.name as initial_name + FROM users u + where u.id = 1 +), +second_user AS ( + SELECT + u.id as second_id, + u.name as second_name + FROM users u + where u.id = 2 +) +SELECT * from initial_user iu +cross join second_user su; + +CREATE OR REPLACE FUNCTION public.get_user_audit_setof_single_row(user_row users) +RETURNS SETOF users_audit +LANGUAGE SQL STABLE +ROWS 1 +AS $$ + SELECT * FROM public.users_audit WHERE user_id = user_row.id; +$$; + +CREATE OR REPLACE FUNCTION public.get_todos_by_matview(todos_matview) +RETURNS SETOF todos ROWS 1 +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos LIMIT 1; +$$; + +CREATE OR REPLACE FUNCTION public.search_todos_by_details(search_details text) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE details ilike search_details; +$$; + +CREATE OR REPLACE FUNCTION public.get_todos_setof_rows(user_row users) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE "user-id" = user_row.id; +$$; + +CREATE OR REPLACE FUNCTION public.get_todos_setof_rows(todo_row todos) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE "user-id" = todo_row."user-id"; +$$; + +-- SETOF composite_type - Returns multiple rows of a custom composite type +CREATE OR REPLACE FUNCTION public.get_composite_type_data() +RETURNS SETOF composite_type_with_array_attribute +LANGUAGE SQL STABLE +AS $$ + SELECT ROW(ARRAY['hello', 'world']::text[])::composite_type_with_array_attribute + UNION ALL + SELECT ROW(ARRAY['foo', 'bar']::text[])::composite_type_with_array_attribute; +$$; + +-- SETOF record - Returns multiple rows with structure defined in the function +CREATE OR REPLACE FUNCTION public.get_user_summary() +RETURNS SETOF record +LANGUAGE SQL STABLE +AS $$ + SELECT u.id, name, count(t.id) as todo_count + FROM public.users u + LEFT JOIN public.todos t ON t."user-id" = u.id + GROUP BY u.id, u.name; +$$; + +-- SETOF scalar_type - Returns multiple values of a basic type +CREATE OR REPLACE FUNCTION public.get_user_ids() +RETURNS SETOF bigint +LANGUAGE SQL STABLE +AS $$ + SELECT id FROM public.users; +$$; + + +-- Function returning view using scalar as input +CREATE OR REPLACE FUNCTION public.get_single_user_summary_from_view(search_user_id bigint) +RETURNS SETOF user_todos_summary_view +LANGUAGE SQL STABLE +ROWS 1 +AS $$ + SELECT * FROM user_todos_summary_view WHERE user_id = search_user_id; +$$; +-- Function returning view using table row as input +CREATE OR REPLACE FUNCTION public.get_single_user_summary_from_view(user_row users) +RETURNS SETOF user_todos_summary_view +LANGUAGE SQL STABLE +ROWS 1 +AS $$ + SELECT * FROM user_todos_summary_view WHERE user_id = user_row.id; +$$; +-- Function returning view using another view row as input +CREATE OR REPLACE FUNCTION public.get_single_user_summary_from_view(userview_row users_view) +RETURNS SETOF user_todos_summary_view +LANGUAGE SQL STABLE +ROWS 1 +AS $$ + SELECT * FROM user_todos_summary_view WHERE user_id = userview_row.id; +$$; + + +-- Function returning view using scalar as input +CREATE OR REPLACE FUNCTION public.get_todos_from_user(search_user_id bigint) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM todos WHERE "user-id" = search_user_id; +$$; +-- Function returning view using table row as input +CREATE OR REPLACE FUNCTION public.get_todos_from_user(user_row users) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM todos WHERE "user-id" = user_row.id; +$$; +-- Function returning view using another view row as input +CREATE OR REPLACE FUNCTION public.get_todos_from_user(userview_row users_view) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM todos WHERE "user-id" = userview_row.id; +$$; + +-- Valid postgresql function override but that produce an unresolvable postgrest function call +create function postgrest_unresolvable_function() returns void language sql as ''; +create function postgrest_unresolvable_function(a text) returns int language sql as 'select 1'; +create function postgrest_unresolvable_function(a int) returns text language sql as $$ + SELECT 'toto' +$$; +-- Valid postgresql function override with differents returns types depending of different arguments +create function postgrest_resolvable_with_override_function() returns void language sql as ''; +create function postgrest_resolvable_with_override_function(a text) returns int language sql as 'select 1'; +create function postgrest_resolvable_with_override_function(b int) returns text language sql as $$ + SELECT 'toto' +$$; +-- Function overrides returning setof tables +create function postgrest_resolvable_with_override_function(user_id bigint) returns setof users language sql stable as $$ + SELECT * FROM users WHERE id = user_id; +$$; +create function postgrest_resolvable_with_override_function(todo_id bigint, completed boolean) returns setof todos language sql stable as $$ + SELECT * FROM todos WHERE id = todo_id AND completed = completed; +$$; +-- Function override taking a table as argument and returning a setof +create function postgrest_resolvable_with_override_function(user_row users) returns setof todos language sql stable as $$ + SELECT * FROM todos WHERE "user-id" = user_row.id; +$$; + +create or replace function public.polymorphic_function_with_different_return(bool) returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_different_return(int) returns int language sql as 'SELECT 2'; +create or replace function public.polymorphic_function_with_different_return(text) returns text language sql as $$ SELECT 'foo' $$; + +create or replace function public.polymorphic_function_with_no_params_or_unnamed() returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_no_params_or_unnamed(bool) returns int language sql as 'SELECT 2'; +create or replace function public.polymorphic_function_with_no_params_or_unnamed(text) returns text language sql as $$ SELECT 'foo' $$; +-- Function with a single unnamed params that isn't a json/jsonb/text should never appears in the type gen as it won't be in postgrest schema +create or replace function public.polymorphic_function_with_unnamed_integer(int) returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_unnamed_json(json) returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_unnamed_jsonb(jsonb) returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_unnamed_text(text) returns int language sql as 'SELECT 1'; + +-- Functions with unnamed parameters that have default values +create or replace function public.polymorphic_function_with_unnamed_default() returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_unnamed_default(int default 42) returns int language sql as 'SELECT 2'; +create or replace function public.polymorphic_function_with_unnamed_default(text default 'default') returns text language sql as $$ SELECT 'foo' $$; + +-- Functions with unnamed parameters that have default values and multiple overloads +create or replace function public.polymorphic_function_with_unnamed_default_overload() returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_unnamed_default_overload(int default 42) returns int language sql as 'SELECT 2'; +create or replace function public.polymorphic_function_with_unnamed_default_overload(text default 'default') returns text language sql as $$ SELECT 'foo' $$; +create or replace function public.polymorphic_function_with_unnamed_default_overload(bool default true) returns int language sql as 'SELECT 3'; + +-- Test function with unnamed row parameter returning setof +CREATE OR REPLACE FUNCTION public.test_unnamed_row_setof(todos) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE "user-id" = $1."user-id"; +$$; + +CREATE OR REPLACE FUNCTION public.test_unnamed_row_setof(users) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE "user-id" = $1."id"; +$$; + + +CREATE OR REPLACE FUNCTION public.test_unnamed_row_setof(user_id bigint) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE "user-id" = user_id; +$$; + +-- Test function with unnamed row parameter returning scalar +CREATE OR REPLACE FUNCTION public.test_unnamed_row_scalar(todos) +RETURNS integer +LANGUAGE SQL STABLE +AS $$ + SELECT COUNT(*) FROM public.todos WHERE "user-id" = $1."user-id"; +$$; + +-- Test function with unnamed view row parameter +CREATE OR REPLACE FUNCTION public.test_unnamed_view_row(todos_view) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE "user-id" = $1."user-id"; +$$; + +-- Test function with multiple unnamed row parameters +CREATE OR REPLACE FUNCTION public.test_unnamed_multiple_rows(users, todos) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos + WHERE "user-id" = $1.id + AND id = $2.id; +$$; + +-- Test function with unnamed row parameter returning composite +CREATE OR REPLACE FUNCTION public.test_unnamed_row_composite(users) +RETURNS composite_type_with_array_attribute +LANGUAGE SQL STABLE +AS $$ + SELECT ROW(ARRAY[$1.name])::composite_type_with_array_attribute; +$$; + +-- Function that returns a single element +CREATE OR REPLACE FUNCTION public.function_using_table_returns(user_row users) +RETURNS todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE todos."user-id" = user_row.id LIMIT 1; +$$; + +CREATE OR REPLACE FUNCTION public.function_using_setof_rows_one(user_row users) +RETURNS SETOF todos +LANGUAGE SQL STABLE +ROWS 1 +AS $$ + SELECT * FROM public.todos WHERE todos."user-id" = user_row.id LIMIT 1; +$$; + +-- Function that return the created_ago computed field +CREATE OR REPLACE FUNCTION "public"."created_ago" ("public"."users_audit") RETURNS numeric LANGUAGE "sql" +SET + "search_path" TO '' AS $_$ + SELECT ROUND(EXTRACT(EPOCH FROM (NOW() - $1.created_at))); +$_$; + +-- Create a partitioned table for testing computed fields on partitioned tables +CREATE TABLE public.events ( + id bigint generated by default as identity, + created_at timestamptz default now(), + event_type text, + data jsonb, + primary key (id, created_at) +) partition by range (created_at); + +-- Create partitions for the events table +CREATE TABLE public.events_2024 PARTITION OF public.events +FOR VALUES FROM ('2024-01-01') TO ('2025-01-01'); + +CREATE TABLE public.events_2025 PARTITION OF public.events +FOR VALUES FROM ('2025-01-01') TO ('2026-01-01'); + +-- Insert some test data +INSERT INTO public.events (created_at, event_type, data) +VALUES + ('2024-06-15', 'login', '{"user": "alice"}'), + ('2025-03-20', 'logout', '{"user": "bob"}'); + +-- Function that returns computed field for partitioned table +CREATE OR REPLACE FUNCTION "public"."days_since_event" ("public"."events") RETURNS numeric LANGUAGE "sql" +SET + "search_path" TO '' AS $_$ + SELECT ROUND(EXTRACT(EPOCH FROM (NOW() - $1.created_at)) / 86400); +$_$; \ No newline at end of file diff --git a/test/postgres/mnt/01-memes.sql b/test/db/01-memes.sql similarity index 100% rename from test/postgres/mnt/01-memes.sql rename to test/db/01-memes.sql diff --git a/test/db/Dockerfile b/test/db/Dockerfile new file mode 100644 index 00000000..fb81d611 --- /dev/null +++ b/test/db/Dockerfile @@ -0,0 +1,3 @@ +FROM supabase/postgres:14.1.0 + +COPY --chown=postgres:postgres --chmod=600 server.key server.crt /var/lib/postgresql/ diff --git a/test/db/docker-compose.yml b/test/db/docker-compose.yml new file mode 100755 index 00000000..d64c92b4 --- /dev/null +++ b/test/db/docker-compose.yml @@ -0,0 +1,16 @@ +services: + db: + build: . + ports: + - 5432:5432 + volumes: + - .:/docker-entrypoint-initdb.d + environment: + POSTGRES_PASSWORD: postgres + command: postgres -c config_file=/etc/postgresql/postgresql.conf -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 1s + timeout: 2s + retries: 10 + start_period: 2s diff --git a/test/db/server.crt b/test/db/server.crt new file mode 100644 index 00000000..26118694 --- /dev/null +++ b/test/db/server.crt @@ -0,0 +1,77 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 41:a3:0e:d5:2f:07:82:95:c1:cc:c8:62:02:04:eb:7b:25:dc:3e:6b + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN = localhost + Validity + Not Before: Aug 2 10:31:43 2023 GMT + Not After : Jul 30 10:31:43 2033 GMT + Subject: CN = localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:d6:4a:ec:0d:40:a8:b1:cd:e8:e9:16:41:a9:6b: + ed:7f:c4:ee:4e:b6:4e:83:5a:7c:37:81:8c:fd:90: + 07:da:57:d3:1b:91:2f:77:6d:a1:b0:38:48:08:03: + 1f:77:91:6a:91:39:54:06:87:20:33:c2:d9:20:e4: + 06:15:f9:59:fb:0e:db:2e:a0:81:c0:6c:47:f6:bc: + 00:0f:07:9a:36:a8:4c:c3:62:97:51:31:53:53:51: + 2a:d6:ff:ca:e6:cf:b2:8e:d7:89:ae:2b:a4:15:ed: + 7c:35:8e:5b:26:84:b1:4d:13:7a:3e:32:a3:56:53: + c1:e8:98:f2:4a:03:56:53:2e:db:c7:96:7e:d2:df: + ea:e5:d7:c2:35:93:61:0d:af:0c:c0:2e:b4:b2:a2: + b1:5a:8b:38:fa:e6:1c:c7:1e:20:d8:0e:b2:97:f2: + 82:6b:4a:1f:27:8c:c1:e4:63:df:42:9a:e3:6c:46: + 74:46:fb:f5:0e:12:d4:b9:12:ce:dc:22:dd:f0:5c: + 6e:e3:31:4f:1a:fa:de:31:15:ec:2a:9b:6c:ea:67: + bf:67:f7:13:44:ba:01:4a:dd:76:32:a8:59:82:13: + 81:f2:48:6d:f4:5d:f0:70:a1:7b:f0:be:46:3e:65: + 36:ee:f3:2e:39:00:52:2a:00:f3:d3:83:c9:55:56: + dd:93 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 79:57:F3:18:B8:6B:FB:64:39:B0:E8:CC:24:18:ED:C0:C1:37:E2:0D + X509v3 Authority Key Identifier: + 79:57:F3:18:B8:6B:FB:64:39:B0:E8:CC:24:18:ED:C0:C1:37:E2:0D + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 2b:d1:37:75:b5:92:9a:c9:ed:45:a6:46:ac:97:93:b9:bf:c0: + f3:7f:47:c3:bd:fd:bd:6b:58:ad:49:79:9d:31:18:3c:b9:94: + 4b:aa:ca:49:c9:04:c4:71:1f:62:9b:ce:3f:5a:24:ec:82:68: + a7:74:45:dd:b1:02:8a:f0:f2:4f:7f:3d:28:94:b0:5b:47:51: + f3:12:a5:ce:1b:32:9f:f8:c6:6a:61:c6:99:4c:f6:99:9e:44: + e4:e9:01:0c:45:1c:a4:5f:f3:69:2e:3d:a7:5d:62:ab:fb:e4: + ea:d2:56:0f:56:df:00:5d:fa:9e:62:2a:77:00:cd:cd:b4:d8: + b6:47:4b:84:73:85:3e:eb:4c:3e:2b:67:46:84:b1:22:1a:04: + 47:02:ca:a0:74:a5:97:28:89:56:aa:c6:4a:ce:97:9b:14:14: + 96:d7:26:60:38:fd:ec:ae:7d:ea:47:68:16:1c:ee:47:19:10: + 69:6a:25:67:71:ac:0b:f0:4a:b0:b3:e6:9b:5f:89:e8:e7:64: + f7:92:37:0c:72:8c:d0:32:c5:10:79:c1:2e:22:05:65:50:db: + d8:0e:bf:b6:d9:f1:7b:88:82:0e:be:06:9b:8c:96:e2:53:03: + 1f:de:86:39:d8:7e:4b:48:bb:11:d9:5d:41:68:82:49:e4:2b: + 33:79:1b:78 +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUQaMO1S8HgpXBzMhiAgTreyXcPmswDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMDgwMjEwMzE0M1oXDTMzMDcz +MDEwMzE0M1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA1krsDUCosc3o6RZBqWvtf8TuTrZOg1p8N4GM/ZAH2lfT +G5Evd22hsDhICAMfd5FqkTlUBocgM8LZIOQGFflZ+w7bLqCBwGxH9rwADweaNqhM +w2KXUTFTU1Eq1v/K5s+yjteJriukFe18NY5bJoSxTRN6PjKjVlPB6JjySgNWUy7b +x5Z+0t/q5dfCNZNhDa8MwC60sqKxWos4+uYcxx4g2A6yl/KCa0ofJ4zB5GPfQprj +bEZ0Rvv1DhLUuRLO3CLd8Fxu4zFPGvreMRXsKpts6me/Z/cTRLoBSt12MqhZghOB +8kht9F3wcKF78L5GPmU27vMuOQBSKgDz04PJVVbdkwIDAQABo1MwUTAdBgNVHQ4E +FgQUeVfzGLhr+2Q5sOjMJBjtwME34g0wHwYDVR0jBBgwFoAUeVfzGLhr+2Q5sOjM +JBjtwME34g0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAK9E3 +dbWSmsntRaZGrJeTub/A839Hw739vWtYrUl5nTEYPLmUS6rKSckExHEfYpvOP1ok +7IJop3RF3bECivDyT389KJSwW0dR8xKlzhsyn/jGamHGmUz2mZ5E5OkBDEUcpF/z +aS49p11iq/vk6tJWD1bfAF36nmIqdwDNzbTYtkdLhHOFPutMPitnRoSxIhoERwLK +oHSllyiJVqrGSs6XmxQUltcmYDj97K596kdoFhzuRxkQaWolZ3GsC/BKsLPmm1+J +6Odk95I3DHKM0DLFEHnBLiIFZVDb2A6/ttnxe4iCDr4Gm4yW4lMDH96GOdh+S0i7 +EdldQWiCSeQrM3kbeA== +-----END CERTIFICATE----- diff --git a/test/db/server.key b/test/db/server.key new file mode 100644 index 00000000..451756ed --- /dev/null +++ b/test/db/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDWSuwNQKixzejp +FkGpa+1/xO5Otk6DWnw3gYz9kAfaV9MbkS93baGwOEgIAx93kWqROVQGhyAzwtkg +5AYV+Vn7DtsuoIHAbEf2vAAPB5o2qEzDYpdRMVNTUSrW/8rmz7KO14muK6QV7Xw1 +jlsmhLFNE3o+MqNWU8HomPJKA1ZTLtvHln7S3+rl18I1k2ENrwzALrSyorFaizj6 +5hzHHiDYDrKX8oJrSh8njMHkY99CmuNsRnRG+/UOEtS5Es7cIt3wXG7jMU8a+t4x +Fewqm2zqZ79n9xNEugFK3XYyqFmCE4HySG30XfBwoXvwvkY+ZTbu8y45AFIqAPPT +g8lVVt2TAgMBAAECggEAHXE9I3OpzzF3pGbEGMSqZJlTFgoi7sCE5pTBy/4jsL0g +/92fxEHngDBgvTETUWNFCApKCtI6phdJq8+IgoZi9YU3Wh2qwMcKetJJE8eQnvKF +XCb0nAQx6vWbnt9AKnGI7+qZ5mM6moSplyt68eeIpgqyC0+mdMWck8TygnbDlTlP +W+lfAZoCnrPDe6ptKTKtSy3AdGteAKk0pdaiUPHjtMdtOMwXCcHQkKopVIstfAib +mvg2/3djn5OnYBmhOINIAZvNSoVr/s9I/yZc8V3z2/lPoLDRmEjCgIGba4zkG0Sr +oaHdxJz8eTuSPwI+jcjto3gPkBdL2658l4JLxXYgQQKBgQD+VWv+jJsB01ijZsI9 +cV1aS6bqyb5sJEc1EFOZtkUYEr0RB6ww4FrRY7uryMPjXf+y47RvGsev0GvWkpRZ +ijzGmfeqHMm9y+hjVxJ64nNOvxzpuVWG0s3JOBDnVY/4RmnW1qghlAI0QkwU7EHl +O4ql3QS5PQEzudhNpQltDHmL4QKBgQDXsleHOzf32HCFR3EWAy+rosuiianGu3LI +2toAX0NxCSkNCPHksfcEryoyrgKLCSzNoBtQMQkvk9sgbQfRLPZi3Lcng+wzjBEv +4uR/a2xdOwnCMCYc9KMjnVukhf5WZ+hJBc49lCqJtc4Mhl89icgrXxUG8YwqUqNK +qb9YMCH38wKBgE3JOnpj7pSkWxu+tfGs1mxjbu2oPkE85zpnf+onQQKX2JN40UUx +mRUpd6CWirLjcOz5j5nbiu9Ow2yg8BZinSvwszqoC1utHaokW1aSI8oV0XX6ZRoT +JzU/nIvkM2AvyPcYN9vtNK9fB33utEiz6TfJXUR6T//N+0XkD/n2MsaBAoGBALDY +A3NYVhbaWcasQEdv7VGnc5WbkJrjbMTIyhur/ztZ61JIlyqNzp0EkHBkwqkDqLwe +HMaurX1YmDwJqHMTjh6YH4JCYxIQMLc2K2lcxcfac7HGkDkFSgwVI+HMCi8Fmijk +nadXJ1koufsC4Gsv3/HPTwoWWHkKr96zNbI0JGWJAoGAFFw+fjx4gI+VDayf4NMd +feIpDF6O2uB9uKbTyNJjYoj9Jh0NkSHccgVb+j5BvnxBmAJHrMEr6Cz3bnlKlK0a +1+Oqyq8MaYRLk6J/xMGSUcfa3uRC5svq0s8ebbl84Kt23IW9NU+YycVnMzysMLsH +xn4VooZdfd3oNm2lpYURz3I= +-----END PRIVATE KEY----- diff --git a/test/extensions.test.ts b/test/extensions.test.ts new file mode 100644 index 00000000..f6966475 --- /dev/null +++ b/test/extensions.test.ts @@ -0,0 +1,144 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/extensions', () => { + test('should list extensions', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/extensions', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list extensions with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/extensions?limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should return 404 for non-existent extension', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/extensions/non-existent-extension', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) + + test('should create extension, retrieve, update, delete', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/extensions', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { name: 'pgcrypto', version: '1.3' }, + }) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchInlineSnapshot(` + { + "comment": "cryptographic functions", + "default_version": "1.3", + "installed_version": "1.3", + "name": "pgcrypto", + "schema": "public", + } + `) + + const retrieveResponse = await app.inject({ + method: 'GET', + url: '/extensions/pgcrypto', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(retrieveResponse.statusCode).toBe(200) + expect(retrieveResponse.json()).toMatchInlineSnapshot(` + { + "comment": "cryptographic functions", + "default_version": "1.3", + "installed_version": "1.3", + "name": "pgcrypto", + "schema": "public", + } + `) + + const updateResponse = await app.inject({ + method: 'PATCH', + url: '/extensions/pgcrypto', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { schema: 'public' }, + }) + expect(updateResponse.statusCode).toBe(200) + expect(updateResponse.json()).toMatchInlineSnapshot(` + { + "comment": "cryptographic functions", + "default_version": "1.3", + "installed_version": "1.3", + "name": "pgcrypto", + "schema": "public", + } + `) + + const deleteResponse = await app.inject({ + method: 'DELETE', + url: '/extensions/pgcrypto', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(deleteResponse.statusCode).toBe(200) + expect(deleteResponse.json()).toMatchInlineSnapshot(` + { + "comment": "cryptographic functions", + "default_version": "1.3", + "installed_version": "1.3", + "name": "pgcrypto", + "schema": "public", + } + `) + + await app.close() + }) + + test('should return 400 for invalid extension name', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/extensions', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { name: 'invalid-extension', version: '1.3' }, + }) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "could not open extension control file "/usr/share/postgresql/14/extension/invalid-extension.control": No such file or directory", + } + `) + await app.close() + }) +}) diff --git a/test/functions.test.ts b/test/functions.test.ts new file mode 100644 index 00000000..f37e850e --- /dev/null +++ b/test/functions.test.ts @@ -0,0 +1,205 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/functions', () => { + test('should list functions', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/functions', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list functions with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/functions?include_system_schemas=true&limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should return 404 for non-existent function', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/functions/non-existent-function', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) + + test('should create function, retrieve, update, delete', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/functions', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_function', + schema: 'public', + language: 'plpgsql', + definition: 'BEGIN RETURN 42; END;', + return_type: 'integer', + }, + }) + expect(response.statusCode).toBe(200) + const responseData = response.json() + expect(responseData).toMatchObject({ + args: [], + argument_types: '', + behavior: 'VOLATILE', + complete_statement: expect.stringContaining( + 'CREATE OR REPLACE FUNCTION public.test_function()' + ), + config_params: null, + definition: 'BEGIN RETURN 42; END;', + id: expect.any(Number), + identity_argument_types: '', + is_set_returning_function: false, + language: 'plpgsql', + name: 'test_function', + return_type: 'integer', + return_type_id: 23, + return_type_relation_id: null, + schema: 'public', + security_definer: false, + }) + + const { id } = responseData + + const retrieveResponse = await app.inject({ + method: 'GET', + url: `/functions/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(retrieveResponse.statusCode).toBe(200) + const retrieveData = retrieveResponse.json() + expect(retrieveData).toMatchObject({ + args: [], + argument_types: '', + behavior: 'VOLATILE', + complete_statement: expect.stringContaining( + 'CREATE OR REPLACE FUNCTION public.test_function()' + ), + config_params: null, + definition: 'BEGIN RETURN 42; END;', + id: expect.any(Number), + identity_argument_types: '', + is_set_returning_function: false, + language: 'plpgsql', + name: 'test_function', + return_type: 'integer', + return_type_id: 23, + return_type_relation_id: null, + schema: 'public', + security_definer: false, + }) + + const updateResponse = await app.inject({ + method: 'PATCH', + url: `/functions/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_function', + schema: 'public', + language: 'plpgsql', + definition: 'BEGIN RETURN 50; END;', + return_type: 'integer', + }, + }) + expect(updateResponse.statusCode).toBe(200) + const updateData = updateResponse.json() + expect(updateData).toMatchObject({ + args: [], + argument_types: '', + behavior: 'VOLATILE', + complete_statement: expect.stringContaining( + 'CREATE OR REPLACE FUNCTION public.test_function()' + ), + config_params: null, + definition: 'BEGIN RETURN 50; END;', + id: expect.any(Number), + identity_argument_types: '', + is_set_returning_function: false, + language: 'plpgsql', + name: 'test_function', + return_type: 'integer', + return_type_id: 23, + return_type_relation_id: null, + schema: 'public', + security_definer: false, + }) + + const deleteResponse = await app.inject({ + method: 'DELETE', + url: `/functions/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(deleteResponse.statusCode).toBe(200) + const deleteData = deleteResponse.json() + expect(deleteData).toMatchObject({ + args: [], + argument_types: '', + behavior: 'VOLATILE', + complete_statement: expect.stringContaining( + 'CREATE OR REPLACE FUNCTION public.test_function()' + ), + config_params: null, + definition: 'BEGIN RETURN 50; END;', + id: expect.any(Number), + identity_argument_types: '', + is_set_returning_function: false, + language: 'plpgsql', + name: 'test_function', + return_type: 'integer', + return_type_id: 23, + return_type_relation_id: null, + schema: 'public', + security_definer: false, + }) + }) + + test('should return 400 for invalid payload', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/functions', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_function12', + }, + }) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "syntax error at or near "NULL"", + } + `) + }) +}) diff --git a/test/index.test.ts b/test/index.test.ts new file mode 100644 index 00000000..d879d232 --- /dev/null +++ b/test/index.test.ts @@ -0,0 +1,38 @@ +// TODO: Change lib tests to server tests. +// https://github.com/supabase/postgres-meta/issues/397#issuecomment-1285078489 +import './lib/columns' +import './lib/config' +import './lib/extensions' +import './lib/foreign-tables' +import './lib/functions' +import './lib/policies' +import './lib/publications' +import './lib/roles' +import './lib/schemas' +import './lib/secrets' +import './lib/tables' +import './lib/triggers' +import './lib/types' +import './lib/version' +import './lib/views' +import './server/column-privileges' +import './server/indexes' +import './server/materialized-views' +import './server/query' +import './server/ssl' +import './server/table-privileges' +import './server/typegen' +import './server/result-size-limit' +import './server/query-timeout' +// New tests for increased coverage - commented out to avoid import issues +// import './server/app' +// import './server/utils' +// import './server/functions' +// import './server/config' +// import './server/extensions' +// import './server/publications' +// import './server/schemas' +// import './server/roles' +// import './server/triggers' +// import './server/types' +// import './server/views' diff --git a/test/integration/index.spec.js b/test/integration/index.spec.js deleted file mode 100644 index 94d7eaf2..00000000 --- a/test/integration/index.spec.js +++ /dev/null @@ -1,1008 +0,0 @@ -var assert = require('assert') -const CryptoJS = require('crypto-js') -import axios from 'axios' -import { PG_META_PORT, PG_CONNECTION, CRYPTO_KEY } from '../../bin/src/server/constants' - -const URL = `http://localhost:${PG_META_PORT}` -const STATUS = { - SUCCESS: 200, - ERROR: 500, -} - -console.log('Running tests on ', URL) - -describe('/', () => { - it('GET', async () => { - const res = await axios.get(`${URL}/`) - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(!!res.data.version, true) - }) -}) -describe('/health', () => { - it('GET', async () => { - const res = await axios.get(`${URL}/health`) - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(!!res.data.date, true) - }) -}) -describe('When passing an encrypted connection header', () => { - it('should decrypt the connection and return a result', async () => { - const encrypted = CryptoJS.AES.encrypt(PG_CONNECTION, CRYPTO_KEY).toString() - const res = await axios.get(`${URL}/config`, { - headers: { - 'X-Connection-Encrypted': encrypted, - }, - }) - // console.log('res.data', res.data) - const datum = res.data.find((x) => x.name == 'trace_recovery_messages') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(true, !!datum) - }) - it('should fail with a bad connection', async () => { - const encrypted = CryptoJS.AES.encrypt('bad connection', CRYPTO_KEY).toString() - try { - const res = await axios.get(`${URL}/config`, { - headers: { - 'X-Connection-Encrypted': encrypted, - }, - }) - assert.strictEqual(res.status, STATUS.ERROR) - } catch (error) { - // console.log('error', error) - assert.strictEqual(error.response.status, STATUS.ERROR) - } - }) -}) -describe('should give meaningful errors', () => { - it('POST', async () => { - try { - const res = await axios.post(`${URL}/query`, { query: 'drop table fake_table' }) - assert.strictEqual(res.data.body, 'Code block should not be reached') // Error should be thrown before executing - } catch (error) { - let { status, data } = error.response - assert.strictEqual(status, 400) - assert.strictEqual(data.error, 'table "fake_table" does not exist') - } - }) -}) -describe('/query', () => { - it('POST', async () => { - const res = await axios.post(`${URL}/query`, { query: 'SELECT * FROM USERS' }) - // console.log('res.data', res.data) - const datum = res.data.find((x) => x.id == 1) - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(datum.name, 'Joe Bloggs') - }) -}) -describe('/config', () => { - it('GET', async () => { - const res = await axios.get(`${URL}/config`) - // console.log('res.data', res.data) - const datum = res.data.find((x) => x.name == 'trace_recovery_messages') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(true, !!datum) - }) -}) -describe('/config/version', () => { - it('GET', async () => { - const res = await axios.get(`${URL}/config/version`) - // console.log('res.data', res.data) - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.match(`${res.data.version_number}`, /^\d{6}$/) - }) -}) -describe('/schemas', () => { - it('GET', async () => { - const res = await axios.get(`${URL}/schemas`) - // console.log('res.data', res.data) - const datum = res.data.find((x) => x.name == 'public') - const notIncluded = res.data.find((x) => x.name == 'pg_toast') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(true, !!datum) - assert.strictEqual(true, !notIncluded) - }) - it('GET with system schemas', async () => { - const res = await axios.get(`${URL}/schemas?include_system_schemas=true`) - // console.log('res.data', res.data) - const datum = res.data.find((x) => x.name == 'public') - const included = res.data.find((x) => x.name == 'pg_toast') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(true, !!datum) - assert.strictEqual(true, !!included) - }) - it('GET without system schemas (explicit)', async () => { - const res = await axios.get(`${URL}/schemas?include_system_schemas=false`) - const isIncluded = res.data.some((x) => x.name === 'pg_catalog') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(isIncluded, false) - }) - it('POST & PATCH & DELETE', async () => { - const res = await axios.post(`${URL}/schemas`, { name: 'api' }) - assert.strictEqual('api', res.data.name) - const newSchemaId = res.data.id - const res2 = await axios.patch(`${URL}/schemas/${newSchemaId}`, { name: 'api_updated' }) - assert.strictEqual('api_updated', res2.data.name) - const res3 = await axios.patch(`${URL}/schemas/${newSchemaId}`, { - name: 'api', - owner: 'postgres', - }) - assert.strictEqual('api', res3.data.name) - - const res4 = await axios.delete(`${URL}/schemas/${newSchemaId}`) - assert.strictEqual(res4.data.name, 'api') - - const res5 = await axios.get(`${URL}/schemas`) - const newSchemaExists = res5.data.some((x) => x.id === newSchemaId) - assert.strictEqual(newSchemaExists, false) - }) -}) -describe('/types', () => { - it('GET', async () => { - const res = await axios.get(`${URL}/types`) - // console.log('res.data', res.data) - const datum = res.data.find((x) => x.schema == 'public') - const notIncluded = res.data.find((x) => x.schema == 'pg_toast') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(true, !!datum) - assert.strictEqual(true, !notIncluded) - }) - it('GET with system types', async () => { - const res = await axios.get(`${URL}/types?include_system_schemas=true`) - // console.log('res.data', res.data) - const datum = res.data.find((x) => x.schema == 'public') - const included = res.data.find((x) => x.schema == 'pg_catalog') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(true, !!datum) - assert.strictEqual(true, !!included) - }) -}) -describe('/functions', () => { - var func = { - id: null, - name: 'test_func', - schema: 'public', - args: ['integer', 'integer'], - definition: 'select $1 + $2', - rettype: 'integer', - language: 'sql', - } - before(async () => { - await axios.post(`${URL}/query`, { - query: `DROP FUNCTION IF EXISTS "${func.name}";`, - }) - await axios.post(`${URL}/query`, { - query: `CREATE SCHEMA IF NOT EXISTS test_schema;`, - }) - }) - after(async () => { - await axios.post(`${URL}/query`, { - query: `DROP SCHEMA IF EXISTS test_schema;`, - }) - }) - it('GET', async () => { - const res = await axios.get(`${URL}/functions`) - // console.log('res.data', res.data) - const datum = res.data.find((x) => x.schema == 'public') - const notIncluded = res.data.find((x) => x.schema == 'pg_toast') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(true, !!datum) - assert.strictEqual(true, !notIncluded) - }) - it('GET with system functions', async () => { - const res = await axios.get(`${URL}/functions?include_system_schemas=true`) - // console.log('res.data', res.data) - const datum = res.data.find((x) => x.schema == 'public') - const included = res.data.find((x) => x.schema == 'pg_catalog') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(true, !!datum) - assert.strictEqual(true, !!included) - }) - it('GET single by ID', async () => { - const functions = await axios.get(`${URL}/functions`) - const functionFiltered = functions.data.find( - (func) => `${func.schema}.${func.name}` === 'public.add' - ) - const { data: functionById } = await axios.get(`${URL}/functions/${functionFiltered.id}`) - - assert.deepStrictEqual(functionById, functionFiltered) - }) - it('POST', async () => { - const { data: newFunc } = await axios.post(`${URL}/functions`, func) - assert.strictEqual(newFunc.name, 'test_func') - assert.strictEqual(newFunc.schema, 'public') - assert.strictEqual(newFunc.language, 'sql') - assert.strictEqual(newFunc.return_type, 'int4') - func.id = newFunc.id - }) - it('PATCH', async () => { - const updates = { - name: 'test_func_renamed', - schema: 'test_schema', - } - - let { data: updated } = await axios.patch(`${URL}/functions/${func.id}`, updates) - assert.strictEqual(updated.id, func.id) - assert.strictEqual(updated.name, 'test_func_renamed') - assert.strictEqual(updated.schema, 'test_schema') - }) - it('DELETE', async () => { - await axios.delete(`${URL}/functions/${func.id}`) - const { data: functions } = await axios.get(`${URL}/functions`) - const stillExists = functions.some((x) => func.id === x.id) - assert.strictEqual(stillExists, false, 'Function is deleted') - }) -}) - -describe('/tables', async () => { - it('GET', async () => { - const tables = await axios.get(`${URL}/tables`) - const datum = tables.data.find((x) => `${x.schema}.${x.name}` === 'public.users') - const memes = tables.data.find((x) => `${x.schema}.${x.name}` === 'public.memes') - const notIncluded = tables.data.find((x) => `${x.schema}.${x.name}` === 'pg_catalog.pg_type') - const idColumn = datum.columns.find((x) => x.name === 'id') - const nameColumn = datum.columns.find((x) => x.name === 'name') - const statusColumn = memes.columns.find((x) => x.name === 'status') - assert.strictEqual(tables.status, STATUS.SUCCESS) - assert.strictEqual(true, !!datum, 'Table included') - assert.strictEqual(true, !notIncluded, 'System table not included') - assert.strictEqual(datum['rls_enabled'], false, 'RLS false') - assert.strictEqual(datum['rls_forced'], false, 'RLS Forced') - assert.strictEqual(datum.columns.length > 0, true, 'Has columns') - assert.strictEqual(datum.primary_keys.length > 0, true, 'Has PK') - assert.strictEqual(idColumn.is_updatable, true, 'Is updatable') - assert.strictEqual(idColumn.is_identity, true, 'ID is Identity') - assert.strictEqual(nameColumn.is_identity, false, 'Name is not identity') - assert.strictEqual(datum.grants.length > 0, true, 'Has grants') - assert.strictEqual(datum.policies.length == 0, true, 'Has no policies') - assert.strictEqual(statusColumn.enums[0], 'new', 'Has enums') - }) - it('GET single by ID', async () => { - const tables = await axios.get(`${URL}/tables`) - const tableFiltered = tables.data.find( - (table) => `${table.schema}.${table.name}` === 'public.users' - ) - const { data: tableById } = await axios.get(`${URL}/tables/${tableFiltered.id}`) - - assert.deepStrictEqual(tableById, tableFiltered) - }) - it('/tables should return the relationships', async () => { - const tables = await axios.get(`${URL}/tables`) - const datum = tables.data.find((x) => `${x.schema}.${x.name}` === 'public.users') - const relationships = datum.relationships - const relationship = relationships.find( - (x) => x.source_schema === 'public' && x.source_table_name === 'todos' - ) - assert.strictEqual(relationships.length > 0, true) - assert.strictEqual(true, relationship.target_table_schema === 'public') - assert.strictEqual(true, relationship.target_table_name === 'users') - }) - it('/tables should return relationships for quoted names', async () => { - const tables = await axios.get(`${URL}/tables`) - const todos = tables.data.find((x) => `${x.schema}.${x.name}` === 'public.todos') - const relationship = todos.relationships.find( - (x) => x.source_schema === 'public' && x.source_table_name === 'todos' - ) - assert.strictEqual(true, relationship.source_column_name === 'user-id') - }) - it('GET /tables with system tables', async () => { - const res = await axios.get(`${URL}/tables?include_system_schemas=true`) - const included = res.data.find((x) => `${x.schema}.${x.name}` === 'pg_catalog.pg_type') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(true, !!included) - }) - it('GET /tables without system tables (explicit)', async () => { - const res = await axios.get(`${URL}/tables?include_system_schemas=false`) - const isIncluded = res.data.some((x) => `${x.schema}.${x.name}` === 'pg_catalog.pg_type') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(isIncluded, false) - }) - it('GET /columns', async () => { - const res = await axios.get(`${URL}/columns`) - // console.log('res.data', res.data) - const datum = res.data.find((x) => x.schema == 'public') - const notIncluded = res.data.find((x) => x.schema == 'pg_catalog') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(true, !!datum) - assert.strictEqual(true, !notIncluded) - }) - it('GET /columns with system types', async () => { - const res = await axios.get(`${URL}/columns?include_system_schemas=true`) - // console.log('res.data', res.data) - const datum = res.data.find((x) => x.schema == 'public') - const included = res.data.find((x) => x.schema == 'pg_catalog') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(true, !!datum) - assert.strictEqual(true, !!included) - }) - it('GET enum /columns with quoted name', async () => { - await axios.post(`${URL}/query`, { - query: 'CREATE TYPE "T" AS ENUM (\'v\'); CREATE TABLE t ( c "T" );', - }) - const { data: columns } = await axios.get(`${URL}/columns`) - const column = columns.find((x) => x.table == 't') - await axios.post(`${URL}/query`, { query: 'DROP TABLE t; DROP TYPE "T";' }) - assert.deepStrictEqual(column.enums.includes('v'), true) - }) - it('POST /tables should create a table', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'test', comment: 'foo' }) - assert.strictEqual(`${newTable.schema}.${newTable.name}`, 'public.test') - assert.strictEqual(newTable.comment, 'foo') - - const { data: tables } = await axios.get(`${URL}/tables`) - const newTableExists = tables.some((table) => table.id === newTable.id) - assert.strictEqual(newTableExists, true) - - await axios.delete(`${URL}/tables/${newTable.id}`) - }) - it('PATCH /tables', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { - name: 'test', - }) - let { data: updatedTable } = await axios.patch(`${URL}/tables/${newTable.id}`, { - name: 'test a', - rls_enabled: true, - rls_forced: true, - replica_identity: 'NOTHING', - comment: 'foo', - }) - assert.strictEqual(updatedTable.name, `test a`) - assert.strictEqual(updatedTable.rls_enabled, true) - assert.strictEqual(updatedTable.rls_forced, true) - assert.strictEqual(updatedTable.replica_identity, 'NOTHING') - assert.strictEqual(updatedTable.comment, 'foo') - await axios.delete(`${URL}/tables/${newTable.id}`) - }) - it('PATCH /tables same name', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { - name: 'test', - }) - let { data: updatedTable } = await axios.patch(`${URL}/tables/${newTable.id}`, { - name: 'test', - }) - assert.strictEqual(updatedTable.name, `test`) - await axios.delete(`${URL}/tables/${newTable.id}`) - }) - it('DELETE /tables', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'test' }) - - await axios.delete(`${URL}/tables/${newTable.id}`) - const { data: tables } = await axios.get(`${URL}/tables`) - const newTableExists = tables.some((table) => table.id === newTable.id) - assert.strictEqual(newTableExists, false) - }) - it('POST /columns', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'foo bar' }) - await axios.post(`${URL}/columns`, { - table_id: newTable.id, - name: 'foo bar', - type: 'int2', - default_value: 42, - is_nullable: false, - comment: 'foo', - }) - - const { data: columns } = await axios.get(`${URL}/columns`) - const newColumn = columns.find( - (column) => - column.id === `${newTable.id}.1` && column.name === 'foo bar' && column.format === 'int2' - ) - assert.strictEqual(newColumn.default_value, "'42'::smallint") - assert.strictEqual(newColumn.is_nullable, false) - assert.strictEqual(newColumn.comment, 'foo') - - await axios.delete(`${URL}/columns/${newTable.id}.1`) - await axios.delete(`${URL}/tables/${newTable.id}`) - }) - it('POST /columns for primary key', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'foo' }) - await axios.post(`${URL}/columns`, { - table_id: newTable.id, - name: 'bar', - type: 'int2', - is_primary_key: true, - }) - - // https://wiki.postgresql.org/wiki/Retrieve_primary_key_columns - const { data: primaryKeys } = await axios.post(`${URL}/query`, { - query: ` - SELECT a.attname - FROM pg_index i - JOIN pg_attribute a ON a.attrelid = i.indrelid - AND a.attnum = ANY(i.indkey) - WHERE i.indrelid = '${newTable.name}'::regclass - AND i.indisprimary; - `, - }) - assert.strictEqual(primaryKeys.length, 1) - assert.strictEqual(primaryKeys[0].attname, 'bar') - - await axios.delete(`${URL}/columns/${newTable.id}.1`) - await axios.delete(`${URL}/tables/${newTable.id}`) - }) - it('POST /columns with unique constraint', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'foo' }) - await axios.post(`${URL}/columns`, { - table_id: newTable.id, - name: 'bar', - type: 'int2', - is_unique: true, - }) - - const { data: uniqueColumns } = await axios.post(`${URL}/query`, { - query: ` - SELECT a.attname - FROM pg_index i - JOIN pg_constraint c ON c.conindid = i.indexrelid - JOIN pg_attribute a ON a.attrelid = i.indrelid - AND a.attnum = ANY(i.indkey) - WHERE i.indrelid = '${newTable.name}'::regclass - AND i.indisunique; - `, - }) - assert.strictEqual(uniqueColumns.length, 1) - assert.strictEqual(uniqueColumns[0].attname, 'bar') - - await axios.delete(`${URL}/columns/${newTable.id}.1`) - await axios.delete(`${URL}/tables/${newTable.id}`) - }) - it('POST /columns array type', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'a' }) - await axios.post(`${URL}/columns`, { - table_id: newTable.id, - name: 'b', - type: 'int2[]', - }) - - const { data: columns } = await axios.get(`${URL}/columns`) - const newColumn = columns.find( - (column) => - column.id === `${newTable.id}.1` && column.name === 'b' && column.format === '_int2' - ) - assert.strictEqual(newColumn.name, 'b') - - await axios.delete(`${URL}/columns/${newTable.id}.1`) - await axios.delete(`${URL}/tables/${newTable.id}`) - }) - it('/columns default_value with expressions', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'a' }) - const { data: newColumn } = await axios.post(`${URL}/columns`, { - table_id: newTable.id, - name: 'a', - type: 'timestamptz', - default_value: 'NOW()', - default_value_format: 'expression', - }) - - assert.strictEqual(newColumn.default_value, 'now()') - - await axios.delete(`${URL}/columns/${newTable.id}.1`) - await axios.delete(`${URL}/tables/${newTable.id}`) - }) - it('POST /columns with constraint definition', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'a' }) - await axios.post(`${URL}/columns`, { - table_id: newTable.id, - name: 'description', - type: 'text', - check: "description <> ''", - }) - - const { data: constraints } = await axios.post(`${URL}/query`, { - query: ` - SELECT pg_get_constraintdef(( - SELECT c.oid - FROM pg_constraint c - WHERE c.conrelid = '${newTable.name}'::regclass - )); - `, - }) - assert.strictEqual(constraints.length, 1) - assert.strictEqual(constraints[0].pg_get_constraintdef, "CHECK ((description <> ''::text))") - - await axios.delete(`${URL}/columns/${newTable.id}.1`) - await axios.delete(`${URL}/tables/${newTable.id}`) - }) - it('PATCH /columns', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'foo bar' }) - await axios.post(`${URL}/columns`, { - table_id: newTable.id, - name: 'foo', - type: 'int2', - default_value: 42, - comment: 'foo', - }) - - await axios.patch(`${URL}/columns/${newTable.id}.1`, { - name: 'foo bar', - type: 'int4', - drop_default: true, - is_identity: true, - identity_generation: 'ALWAYS', - is_nullable: false, - comment: 'bar', - }) - - const { data: columns } = await axios.get(`${URL}/columns`) - const updatedColumn = columns.find( - (column) => - column.id === `${newTable.id}.1` && column.name === 'foo bar' && column.format === 'int4' - ) - assert.strictEqual(updatedColumn.default_value, null) - assert.strictEqual(updatedColumn.identity_generation, 'ALWAYS') - assert.strictEqual(updatedColumn.is_nullable, false) - assert.strictEqual(updatedColumn.comment, 'bar') - - await axios.delete(`${URL}/columns/${newTable.id}.1`) - await axios.delete(`${URL}/tables/${newTable.id}`) - }) - it('PATCH /columns same name', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'foo' }) - await axios.post(`${URL}/columns`, { - table_id: newTable.id, - name: 'bar', - type: 'int2', - }) - - const { data: updatedColumn } = await axios.patch(`${URL}/columns/${newTable.id}.1`, { - name: 'bar', - }) - - assert.strictEqual(updatedColumn.name, 'bar') - - await axios.delete(`${URL}/columns/${newTable.id}.1`) - await axios.delete(`${URL}/tables/${newTable.id}`) - }) - it('PATCH /columns "incompatible" types', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'foo' }) - await axios.post(`${URL}/columns`, { - table_id: newTable.id, - name: 'bar', - type: 'text', - }) - - const { data: updatedColumn } = await axios.patch(`${URL}/columns/${newTable.id}.1`, { - type: 'int4', - default_value: 0, - }) - - assert.strictEqual(updatedColumn.format, 'int4') - - await axios.delete(`${URL}/columns/${newTable.id}.1`) - await axios.delete(`${URL}/tables/${newTable.id}`) - }) - it('DELETE /columns', async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'foo bar' }) - await axios.post(`${URL}/columns`, { table_id: newTable.id, name: 'foo bar', type: 'int2' }) - - await axios.delete(`${URL}/columns/${newTable.id}.1`) - const { data: columns } = await axios.get(`${URL}/columns`) - const newColumnExists = columns.some((column) => column.id === `${newTable.id}.1`) - assert.strictEqual(newColumnExists, false) - - await axios.delete(`${URL}/tables/${newTable.id}`) - }) - - it("allows ' in COMMENTs", async () => { - const { data: newTable } = await axios.post(`${URL}/tables`, { name: 'foo', comment: "'" }) - assert.strictEqual(newTable.comment, "'") - - await axios.post(`${URL}/columns`, { - table_id: newTable.id, - name: 'bar', - type: 'int2', - comment: "'", - }) - - const { data: columns } = await axios.get(`${URL}/columns`) - const newColumn = columns.find( - (column) => - column.id === `${newTable.id}.1` && column.name === 'bar' && column.format === 'int2' - ) - assert.strictEqual(newColumn.comment, "'") - - await axios.delete(`${URL}/columns/${newTable.id}.1`) - await axios.delete(`${URL}/tables/${newTable.id}`) - }) -}) -// TODO: Test for schema (currently checked manually). Need a different SQL template. -describe('/extensions', () => { - it('GET', async () => { - const res = await axios.get(`${URL}/extensions`) - // console.log('res.data', res.data) - const datum = res.data.find((x) => x.name == 'uuid-ossp') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(true, !!datum) - }) - it('POST', async () => { - const { data: extSchema } = await axios.post(`${URL}/schemas`, { name: 'extensions' }) - await axios.post(`${URL}/extensions`, { name: 'hstore', schema: 'extensions', version: '1.4' }) - - const { data: extensions } = await axios.get(`${URL}/extensions`) - const newExtension = extensions.find((ext) => ext.name === 'hstore') - assert.strictEqual(newExtension.installed_version, '1.4') - - await axios.delete(`${URL}/extensions/hstore`) - await axios.delete(`${URL}/schemas/${extSchema.id}`) - }) - it('PATCH', async () => { - const { data: extSchema } = await axios.post(`${URL}/schemas`, { name: 'extensions' }) - await axios.post(`${URL}/extensions`, { name: 'hstore', version: '1.4' }) - - await axios.patch(`${URL}/extensions/hstore`, { update: true, schema: 'extensions' }) - - const { data: extensions } = await axios.get(`${URL}/extensions`) - const updatedExtension = extensions.find((ext) => ext.name === 'hstore') - assert.strictEqual(updatedExtension.installed_version, updatedExtension.default_version) - - await axios.delete(`${URL}/extensions/hstore`) - await axios.delete(`${URL}/schemas/${extSchema.id}`) - }) - it('DELETE', async () => { - await axios.post(`${URL}/extensions`, { name: 'hstore', version: '1.4' }) - - await axios.delete(`${URL}/extensions/hstore`) - const { data: extensions } = await axios.get(`${URL}/extensions`) - const deletedExtension = extensions.find((ext) => ext.name === 'hstore') - assert.strictEqual(deletedExtension.installed_version, null) - }) -}) -describe('/roles', () => { - it('GET', async () => { - const res = await axios.get(`${URL}/roles`) - // console.log('res', res) - const datum = res.data.find((x) => x.name == 'postgres') - const hasSystemSchema = datum.grants.some((x) => x.schema == 'information_schema') - const hasPublicSchema = datum.grants.some((x) => x.schema == 'public') - assert.strictEqual(res.status, STATUS.SUCCESS) - assert.strictEqual(true, !!datum) - assert.strictEqual(hasSystemSchema, false) - assert.strictEqual(hasPublicSchema, true) - }) - it('POST', async () => { - await axios.post(`${URL}/roles`, { - name: 'test', - is_superuser: true, - can_create_db: true, - can_create_role: true, - inherit_role: false, - can_login: true, - is_replication_role: true, - can_bypass_rls: true, - connection_limit: 100, - valid_until: '2020-01-01T00:00:00.000Z', - }) - const { data: roles } = await axios.get(`${URL}/roles`) - const test = roles.find((role) => role.name === 'test') - assert.strictEqual(test.is_superuser, true) - assert.strictEqual(test.can_create_db, true) - assert.strictEqual(test.can_create_role, true) - assert.strictEqual(test.inherit_role, false) - assert.strictEqual(test.can_login, true) - assert.strictEqual(test.is_replication_role, true) - assert.strictEqual(test.can_bypass_rls, true) - assert.strictEqual(test.connection_limit, 100) - assert.strictEqual(test.valid_until, '2020-01-01T00:00:00.000Z') - await axios.post(`${URL}/query`, { query: 'DROP ROLE test;' }) - }) - it('PATCH', async () => { - const { data: newRole } = await axios.post(`${URL}/roles`, { name: 'foo' }) - - await axios.patch(`${URL}/roles/${newRole.id}`, { - name: 'bar', - is_superuser: true, - can_create_db: true, - can_create_role: true, - inherit_role: false, - can_login: true, - is_replication_role: true, - can_bypass_rls: true, - connection_limit: 100, - valid_until: '2020-01-01T00:00:00.000Z', - }) - - const { data: roles } = await axios.get(`${URL}/roles`) - const updatedRole = roles.find((role) => role.id === newRole.id) - assert.strictEqual(updatedRole.name, 'bar') - assert.strictEqual(updatedRole.is_superuser, true) - assert.strictEqual(updatedRole.can_create_db, true) - assert.strictEqual(updatedRole.can_create_role, true) - assert.strictEqual(updatedRole.inherit_role, false) - assert.strictEqual(updatedRole.can_login, true) - assert.strictEqual(updatedRole.is_replication_role, true) - assert.strictEqual(updatedRole.can_bypass_rls, true) - assert.strictEqual(updatedRole.connection_limit, 100) - assert.strictEqual(updatedRole.valid_until, '2020-01-01T00:00:00.000Z') - - await axios.delete(`${URL}/roles/${newRole.id}`) - }) - it('DELETE', async () => { - const { data: newRole } = await axios.post(`${URL}/roles`, { name: 'foo bar' }) - - await axios.delete(`${URL}/roles/${newRole.id}`) - const { data: roles } = await axios.get(`${URL}/roles`) - const newRoleExists = roles.some((role) => role.id === newRole.id) - assert.strictEqual(newRoleExists, false) - }) -}) -describe('/policies', () => { - var policy = { - id: null, - name: 'test policy', - schema: 'public', - table: 'memes', - action: 'RESTRICTIVE', - } - before(async () => { - await axios.post(`${URL}/query`, { - query: `DROP POLICY IF EXISTS "${policy.name}" on "${policy.schema}"."${policy.table}" `, - }) - }) - it('GET', async () => { - const res = await axios.get(`${URL}/policies`) - // console.log('res', res) - const policy = res.data[0] - assert.strictEqual('id' in policy, true, 'Has ID') - assert.strictEqual('name' in policy, true, 'Has name') - assert.strictEqual('action' in policy, true, 'Has action') - assert.strictEqual('table' in policy, true, 'Has table') - assert.strictEqual('table_id' in policy, true, 'Has table_id') - assert.strictEqual('roles' in policy, true, 'Has roles') - assert.strictEqual('command' in policy, true, 'Has command') - assert.strictEqual('definition' in policy, true, 'Has definition') - assert.strictEqual('check' in policy, true, 'Has check') - }) - it('POST', async () => { - const { data: newPolicy } = await axios.post(`${URL}/policies`, policy) - assert.strictEqual(newPolicy.name, 'test policy') - assert.strictEqual(newPolicy.schema, 'public') - assert.strictEqual(newPolicy.table, 'memes') - assert.strictEqual(newPolicy.action, 'RESTRICTIVE') - assert.strictEqual(newPolicy.roles[0], 'public') - assert.strictEqual(newPolicy.command, 'ALL') - assert.strictEqual(newPolicy.definition, null) - assert.strictEqual(newPolicy.check, null) - policy.id = newPolicy.id - }) - it('PATCH', async () => { - const updates = { - name: 'policy updated', - definition: `current_setting('my.username') IN (name)`, - check: `current_setting('my.username') IN (name)`, - } - let { data: updated } = await axios.patch(`${URL}/policies/${policy.id}`, updates) - // console.log('updated', updated) - assert.strictEqual(updated.id, policy.id) - assert.strictEqual(updated.name, 'policy updated', 'name updated') - assert.notEqual(updated.definition, null, 'definition updated') - assert.notEqual(updated.check, null, 'check updated') - }) - it('DELETE', async () => { - await axios.delete(`${URL}/policies/${policy.id}`) - const { data: policies } = await axios.get(`${URL}/policies`) - const stillExists = policies.some((x) => policy.id === x.id) - assert.strictEqual(stillExists, false, 'Policy is deleted') - }) -}) - -describe('/publications with tables', () => { - const publication = { - name: 'a', - publish_insert: true, - publish_update: true, - publish_delete: true, - publish_truncate: false, - tables: ['users'], - } - it('POST', async () => { - const { data: newPublication } = await axios.post(`${URL}/publications`, publication) - assert.strictEqual(newPublication.name, publication.name) - assert.strictEqual(newPublication.publish_insert, publication.publish_insert) - assert.strictEqual(newPublication.publish_update, publication.publish_update) - assert.strictEqual(newPublication.publish_delete, publication.publish_delete) - assert.strictEqual(newPublication.publish_truncate, publication.publish_truncate) - assert.strictEqual( - newPublication.tables.some((table) => `${table.schema}.${table.name}` === 'public.users'), - true - ) - }) - it('GET', async () => { - const res = await axios.get(`${URL}/publications`) - const newPublication = res.data[0] - assert.strictEqual(newPublication.name, publication.name) - }) - it('PATCH', async () => { - const res = await axios.get(`${URL}/publications`) - const { id } = res.data[0] - - const { data: updated } = await axios.patch(`${URL}/publications/${id}`, { - name: 'b', - publish_insert: false, - tables: [], - }) - assert.strictEqual(updated.name, 'b') - assert.strictEqual(updated.publish_insert, false) - assert.strictEqual( - updated.tables.some((table) => `${table.schema}.${table.name}` === 'public.users'), - false - ) - }) - it('DELETE', async () => { - const res = await axios.get(`${URL}/publications`) - const { id } = res.data[0] - - await axios.delete(`${URL}/publications/${id}`) - const { data: publications } = await axios.get(`${URL}/publications`) - const stillExists = publications.some((x) => x.id === id) - assert.strictEqual(stillExists, false) - }) - it('/publications for tables with uppercase', async () => { - const { data: table } = await axios.post(`${URL}/tables`, { name: 'T' }) - const { data: publication } = await axios.post(`${URL}/publications`, { - name: 'pub', - tables: ['T'], - }) - assert.strictEqual(publication.name, 'pub') - const { data: alteredPublication } = await axios.patch( - `${URL}/publications/${publication.id}`, - { tables: ['T'] } - ) - assert.strictEqual(alteredPublication.name, 'pub') - - await axios.delete(`${URL}/publications/${publication.id}`) - await axios.delete(`${URL}/tables/${table.id}`) - }) -}) - -describe('/publications FOR ALL TABLES', () => { - const publication = { - name: 'for_all', - publish_insert: true, - publish_update: true, - publish_delete: true, - publish_truncate: false, - } - it('POST', async () => { - const { data: newPublication } = await axios.post(`${URL}/publications`, publication) - assert.strictEqual(newPublication.name, publication.name) - assert.strictEqual(newPublication.publish_insert, publication.publish_insert) - assert.strictEqual(newPublication.publish_update, publication.publish_update) - assert.strictEqual(newPublication.publish_delete, publication.publish_delete) - assert.strictEqual(newPublication.publish_truncate, publication.publish_truncate) - }) - it('DELETE', async () => { - const res = await axios.get(`${URL}/publications`) - const { id } = res.data[0] - - await axios.delete(`${URL}/publications/${id}`) - const { data: publications } = await axios.get(`${URL}/publications`) - const stillExists = publications.some((x) => x.id === id) - assert.strictEqual(stillExists, false) - }) -}) - -describe('/triggers', () => { - const renamedTriggerName = 'test_trigger_renamed' - const trigger = { - name: 'test_trigger', - schema: 'public', - table: 'users_audit', - function_schema: 'public', - function_name: 'audit_action', - function_args: ['test1', 'test2'], - activation: 'AFTER', - events: ['UPDATE'], - orientation: 'ROW', - condition: '(old.* IS DISTINCT FROM new.*)', - } - const multiEventTrigger = { - ...trigger, - ...{ name: 'test_multi_event_trigger', events: ['insert', 'update', 'delete'], condition: '' }, - } - - before(async () => { - await axios.post(`${URL}/query`, { - query: `DROP TRIGGER IF EXISTS ${trigger.name} on ${trigger.schema}.${trigger.table} CASCADE;`, - }) - await axios.post(`${URL}/query`, { - query: `DROP TRIGGER IF EXISTS ${renamedTriggerName} on ${trigger.schema}.${trigger.table} CASCADE;`, - }) - await axios.post(`${URL}/query`, { - query: `DROP TRIGGER IF EXISTS ${multiEventTrigger.name} on ${multiEventTrigger.schema}.${multiEventTrigger.table} CASCADE;`, - }) - }) - - after(async () => { - await axios.post(`${URL}/query`, { - query: `DROP TRIGGER IF EXISTS ${trigger.name} on ${trigger.schema}.${trigger.table} CASCADE;`, - }) - await axios.post(`${URL}/query`, { - query: `DROP TRIGGER IF EXISTS ${renamedTriggerName} on ${trigger.schema}.${trigger.table} CASCADE;`, - }) - await axios.post(`${URL}/query`, { - query: `DROP TRIGGER IF EXISTS ${multiEventTrigger.name} on ${multiEventTrigger.schema}.${multiEventTrigger.table} CASCADE;`, - }) - }) - - it('POST trigger', async () => { - const { data: triggerRecord } = await axios.post(`${URL}/triggers`, trigger) - - assert.strictEqual(typeof triggerRecord.id, 'number') - assert.strictEqual(triggerRecord.enabled_mode, 'ORIGIN') - assert.strictEqual(triggerRecord.name, 'test_trigger') - assert.strictEqual(triggerRecord.table, 'users_audit') - assert.strictEqual(triggerRecord.schema, 'public') - assert.strictEqual(triggerRecord.condition, '(old.* IS DISTINCT FROM new.*)') - assert.strictEqual(triggerRecord.orientation, 'ROW') - assert.strictEqual(triggerRecord.activation, 'AFTER') - assert.strictEqual(triggerRecord.function_schema, 'public') - assert.strictEqual(triggerRecord.function_name, 'audit_action') - assert.deepStrictEqual(triggerRecord.events, ['UPDATE']) - assert.deepStrictEqual(triggerRecord.function_args, ['test1', 'test2']) - }) - - it('POST multi-event trigger', async () => { - const { data: triggerRecord } = await axios.post(`${URL}/triggers`, multiEventTrigger) - - assert.deepStrictEqual(new Set(triggerRecord.events), new Set(['INSERT', 'UPDATE', 'DELETE'])) - }) - - it('GET', async () => { - const { data: triggerData } = await axios.get(`${URL}/triggers`) - const triggerIds = triggerData.reduce((acc, { id }) => acc.add(id), new Set()) - - assert.strictEqual(triggerData.length, 2) - assert.strictEqual(triggerIds.size, 2) - assert.ok(triggerData.find(({ name }) => name === 'test_trigger')) - assert.ok(triggerData.find(({ name }) => name === 'test_multi_event_trigger')) - - const sortedTriggerData = triggerData.sort((a, b) => a.name.length - b.name.length) - - const { data: singleEventTriggerRecord } = await axios.get( - `${URL}/triggers/${sortedTriggerData[0].id}` - ) - assert.strictEqual(singleEventTriggerRecord.name, 'test_trigger') - - const { data: multiEventTriggerRecord } = await axios.get( - `${URL}/triggers/${sortedTriggerData[1].id}` - ) - assert.strictEqual(multiEventTriggerRecord.name, 'test_multi_event_trigger') - }) - - it('PATCH', async () => { - const { data: triggerData } = await axios.get(`${URL}/triggers`) - const { id, name, enabled_mode } = triggerData.sort((a, b) => a.name.length - b.name.length)[0] - - assert.strictEqual(name, 'test_trigger') - assert.strictEqual(enabled_mode, 'ORIGIN') - - const { data: updatedTriggerRecord } = await axios.patch(`${URL}/triggers/${id}`, { - name: 'test_trigger_renamed', - enabled_mode: 'DISABLED', - }) - - assert.strictEqual(updatedTriggerRecord.name, 'test_trigger_renamed') - assert.strictEqual(updatedTriggerRecord.enabled_mode, 'DISABLED') - - const { data: reEnabledTriggerRecord } = await axios.patch(`${URL}/triggers/${id}`, { - enabled_mode: 'REPLICA', - }) - - assert.strictEqual(reEnabledTriggerRecord.enabled_mode, 'REPLICA') - }) - - it('DELETE', async () => { - const { data: triggerData } = await axios.get(`${URL}/triggers`) - - assert.strictEqual(triggerData.length, 2) - - const triggerIds = triggerData.reduce((acc, { id }) => acc.add(id), new Set()) - - for (const id of triggerIds) { - await axios.delete(`${URL}/triggers/${id}`) - } - - const { data: emptyTriggerData } = await axios.get(`${URL}/triggers`) - - assert.strictEqual(emptyTriggerData.length, 0) - }) -}) diff --git a/test/lib/columns.ts b/test/lib/columns.ts new file mode 100644 index 00000000..3fcac79f --- /dev/null +++ b/test/lib/columns.ts @@ -0,0 +1,1019 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +test('list', async () => { + const res = await pgMeta.columns.list() + expect(res.data?.find(({ name }) => name === 'user-id')).toMatchInlineSnapshot( + { + id: expect.stringMatching(/^\d+\.3$/), + table_id: expect.any(Number), + }, + ` + { + "check": null, + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "id": StringMatching /\\^\\\\d\\+\\\\\\.3\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": false, + "is_unique": false, + "is_updatable": true, + "name": "user-id", + "ordinal_position": 3, + "schema": "public", + "table": "todos", + "table_id": Any, + } + ` + ) +}) + +test('list from a single table', async () => { + const { data: testTable }: any = await pgMeta.tables.create({ name: 't' }) + await pgMeta.query('alter table t add c1 text, add c2 text') + + const res = await pgMeta.columns.list({ tableId: testTable!.id }) + expect(res).toMatchInlineSnapshot( + { + data: [ + { + id: expect.stringMatching(/^\d+\.\d+$/), + table_id: expect.any(Number), + }, + + { + id: expect.stringMatching(/^\d+\.\d+$/), + table_id: expect.any(Number), + }, + ], + }, + ` + { + "data": [ + { + "check": null, + "comment": null, + "data_type": "text", + "default_value": null, + "enums": [], + "format": "text", + "id": StringMatching /\\^\\\\d\\+\\\\\\.\\\\d\\+\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c1", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + { + "check": null, + "comment": null, + "data_type": "text", + "default_value": null, + "enums": [], + "format": "text", + "id": StringMatching /\\^\\\\d\\+\\\\\\.\\\\d\\+\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c2", + "ordinal_position": 2, + "schema": "public", + "table": "t", + "table_id": Any, + }, + ], + "error": null, + } + ` + ) + + await pgMeta.tables.remove(testTable!.id) +}) + +test('list columns with included schemas', async () => { + let res = await pgMeta.columns.list({ + includedSchemas: ['public'], + }) + + expect(res.data?.length).toBeGreaterThan(0) + + res.data?.forEach((column) => { + expect(column.schema).toBe('public') + }) +}) + +test('list columns with excluded schemas', async () => { + let res = await pgMeta.columns.list({ + excludedSchemas: ['public'], + }) + + res.data?.forEach((column) => { + expect(column.schema).not.toBe('public') + }) +}) + +test('list columns with excluded schemas and include System Schemas', async () => { + let res = await pgMeta.columns.list({ + excludedSchemas: ['public'], + includeSystemSchemas: true, + }) + + expect(res.data?.length).toBeGreaterThan(0) + + res.data?.forEach((column) => { + expect(column.schema).not.toBe('public') + }) +}) + +test('retrieve, create, update, delete', async () => { + const { data: testTable }: any = await pgMeta.tables.create({ name: 't' }) + + let res = await pgMeta.columns.create({ + table_id: testTable!.id, + name: 'c', + type: 'int2', + default_value: 42, + comment: 'foo', + }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.stringMatching(/^\d+\.1$/), table_id: expect.any(Number) } }, + ` + { + "data": { + "check": null, + "comment": "foo", + "data_type": "smallint", + "default_value": "'42'::smallint", + "enums": [], + "format": "int2", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.columns.retrieve({ id: res.data!.id }) + expect(res).toMatchInlineSnapshot( + { + data: { id: expect.stringMatching(/^\d+\.1$/), table_id: expect.any(Number) }, + }, + ` + { + "data": { + "check": null, + "comment": "foo", + "data_type": "smallint", + "default_value": "'42'::smallint", + "enums": [], + "format": "int2", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.columns.update(res.data!.id, { + name: 'c1', + type: 'int4', + drop_default: true, + is_identity: true, + identity_generation: 'ALWAYS', + is_nullable: false, + comment: 'bar', + }) + expect(res).toMatchInlineSnapshot( + { + data: { id: expect.stringMatching(/^\d+\.1$/), table_id: expect.any(Number) }, + }, + ` + { + "data": { + "check": null, + "comment": "bar", + "data_type": "integer", + "default_value": null, + "enums": [], + "format": "int4", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": "ALWAYS", + "is_generated": false, + "is_identity": true, + "is_nullable": false, + "is_unique": false, + "is_updatable": true, + "name": "c1", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.columns.remove(res.data!.id) + expect(res).toMatchInlineSnapshot( + { + data: { id: expect.stringMatching(/^\d+\.1$/), table_id: expect.any(Number) }, + }, + ` + { + "data": { + "check": null, + "comment": "bar", + "data_type": "integer", + "default_value": null, + "enums": [], + "format": "int4", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": "ALWAYS", + "is_generated": false, + "is_identity": true, + "is_nullable": false, + "is_unique": false, + "is_updatable": true, + "name": "c1", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.columns.retrieve({ id: res.data!.id }) + expect(res).toMatchObject({ + data: null, + error: { + message: expect.stringMatching(/^Cannot find a column with ID \d+.1$/), + }, + }) + + await pgMeta.tables.remove(testTable!.id) +}) + +test('enum column with quoted name', async () => { + await pgMeta.query('CREATE TYPE "T" AS ENUM (\'v\'); CREATE TABLE t ( c "T" );') + + const res = await pgMeta.columns.list() + expect(res.data!.find(({ table }) => table === 't')).toMatchInlineSnapshot( + { + id: expect.stringMatching(/^\d+\.1$/), + table_id: expect.any(Number), + }, + ` + { + "check": null, + "comment": null, + "data_type": "USER-DEFINED", + "default_value": null, + "enums": [ + "v", + ], + "format": "T", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + } + ` + ) + + await pgMeta.query('DROP TABLE t; DROP TYPE "T";') +}) + +test('primary key column', async () => { + const { data: testTable } = await pgMeta.tables.create({ name: 't' }) + + await pgMeta.columns.create({ + table_id: testTable!.id, + name: 'c', + type: 'int2', + is_primary_key: true, + }) + const res = await pgMeta.query(` +SELECT a.attname +FROM pg_index i +JOIN pg_attribute a ON a.attrelid = i.indrelid + AND a.attnum = ANY(i.indkey) +WHERE i.indrelid = '${testTable!.name}'::regclass +AND i.indisprimary; +`) + expect(res).toMatchInlineSnapshot(` + { + "data": [ + { + "attname": "c", + }, + ], + "error": null, + } + `) + + await pgMeta.tables.remove(testTable!.id) +}) + +test('unique column', async () => { + const { data: testTable } = await pgMeta.tables.create({ name: 't' }) + + await pgMeta.columns.create({ + table_id: testTable!.id, + name: 'c', + type: 'int2', + is_unique: true, + }) + const res = await pgMeta.query(` +SELECT a.attname +FROM pg_index i +JOIN pg_constraint c ON c.conindid = i.indexrelid +JOIN pg_attribute a ON a.attrelid = i.indrelid + AND a.attnum = ANY(i.indkey) +WHERE i.indrelid = '${testTable!.name}'::regclass +AND i.indisunique; +`) + expect(res).toMatchInlineSnapshot(` + { + "data": [ + { + "attname": "c", + }, + ], + "error": null, + } + `) + + await pgMeta.tables.remove(testTable!.id) +}) + +test('array column', async () => { + const { data: testTable } = await pgMeta.tables.create({ name: 't' }) + + const res = await pgMeta.columns.create({ + table_id: testTable!.id, + name: 'c', + type: 'int2[]', + }) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.stringMatching(/^\d+\.1$/), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "check": null, + "comment": null, + "data_type": "ARRAY", + "default_value": null, + "enums": [], + "format": "_int2", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + + await pgMeta.tables.remove(testTable!.id) +}) + +test('column with default value', async () => { + const { data: testTable } = await pgMeta.tables.create({ name: 't' }) + + const res = await pgMeta.columns.create({ + table_id: testTable!.id, + name: 'c', + type: 'timestamptz', + default_value: 'NOW()', + default_value_format: 'expression', + }) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.stringMatching(/^\d+\.1$/), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "check": null, + "comment": null, + "data_type": "timestamp with time zone", + "default_value": "now()", + "enums": [], + "format": "timestamptz", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + + await pgMeta.tables.remove(testTable!.id) +}) + +test('column with constraint', async () => { + const { data: testTable } = await pgMeta.tables.create({ name: 't' }) + + await pgMeta.columns.create({ + table_id: testTable!.id, + name: 'c', + type: 'text', + check: "description <> ''", + }) + const res = await pgMeta.query(` +SELECT pg_get_constraintdef(( + SELECT c.oid + FROM pg_constraint c + WHERE c.conrelid = '${testTable!.name}'::regclass +)); +`) + expect(res).toMatchInlineSnapshot(` + { + "data": [ + { + "pg_get_constraintdef": null, + }, + ], + "error": null, + } + `) + + await pgMeta.tables.remove(testTable!.id) +}) + +test('update with name unchanged', async () => { + const { data: testTable } = await pgMeta.tables.create({ name: 't' }) + + let res = await pgMeta.columns.create({ + table_id: testTable!.id, + name: 'c', + type: 'int2', + }) + res = await pgMeta.columns.update(res.data!.id, { + name: 'c', + }) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.stringMatching(/^\d+\.1$/), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "check": null, + "comment": null, + "data_type": "smallint", + "default_value": null, + "enums": [], + "format": "int2", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + + await pgMeta.tables.remove(testTable!.id) +}) + +test('update with array types', async () => { + const { data: testTable } = await pgMeta.tables.create({ name: 't' }) + + let res = await pgMeta.columns.create({ + table_id: testTable!.id, + name: 'c', + type: 'text', + }) + res = await pgMeta.columns.update(res.data!.id, { + type: 'text[]', + }) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.stringMatching(/^\d+\.1$/), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "check": null, + "comment": null, + "data_type": "ARRAY", + "default_value": null, + "enums": [], + "format": "_text", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + + await pgMeta.tables.remove(testTable!.id) +}) + +test('update with incompatible types', async () => { + const { data: testTable } = await pgMeta.tables.create({ name: 't' }) + + let res = await pgMeta.columns.create({ + table_id: testTable!.id, + name: 'c', + type: 'text', + }) + res = await pgMeta.columns.update(res.data!.id, { + type: 'int4', + }) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.stringMatching(/^\d+\.1$/), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "check": null, + "comment": null, + "data_type": "integer", + "default_value": null, + "enums": [], + "format": "int4", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + + await pgMeta.tables.remove(testTable!.id) +}) + +test('update is_unique', async () => { + const { data: testTable } = await pgMeta.tables.create({ name: 't' }) + + let res = await pgMeta.columns.create({ + table_id: testTable!.id, + name: 'c', + type: 'text', + is_unique: false, + }) + res = await pgMeta.columns.update(res.data!.id, { is_unique: true }) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.stringMatching(/^\d+\.1$/), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "check": null, + "comment": null, + "data_type": "text", + "default_value": null, + "enums": [], + "format": "text", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": true, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.columns.update(res.data!.id, { is_unique: false }) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.stringMatching(/^\d+\.1$/), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "check": null, + "comment": null, + "data_type": "text", + "default_value": null, + "enums": [], + "format": "text", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + + await pgMeta.tables.remove(testTable!.id) +}) + +// https://github.com/supabase/supabase/issues/3553 +test('alter column to type with uppercase', async () => { + const { data: testTable } = await pgMeta.tables.create({ name: 't' }) + await pgMeta.query('CREATE TYPE "T" AS ENUM ()') + + let res = await pgMeta.columns.create({ + table_id: testTable!.id, + name: 'c', + type: 'text', + is_unique: false, + }) + res = await pgMeta.columns.update(res.data!.id, { type: 'T' }) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.stringMatching(/^\d+\.1$/), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "check": null, + "comment": null, + "data_type": "USER-DEFINED", + "default_value": null, + "enums": [], + "format": "T", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + + await pgMeta.tables.remove(testTable!.id) + await pgMeta.query('DROP TYPE "T"') +}) + +test('enums are populated in enum array columns', async () => { + await pgMeta.query(`create type test_enum as enum ('a')`) + const { data: testTable } = await pgMeta.tables.create({ name: 't' }) + + let res = await pgMeta.columns.create({ + table_id: testTable!.id, + name: 'c', + type: '_test_enum', + }) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.stringMatching(/^\d+\.1$/), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "check": null, + "comment": null, + "data_type": "ARRAY", + "default_value": null, + "enums": [ + "a", + ], + "format": "_test_enum", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + + await pgMeta.tables.remove(testTable!.id) + await pgMeta.query(`drop type test_enum`) +}) + +test('drop with cascade', async () => { + await pgMeta.query(` +create table public.t ( + id int8 primary key, + t_id int8 generated always as (id) stored +); +`) + + let res = await pgMeta.columns.retrieve({ + schema: 'public', + table: 't', + name: 'id', + }) + res = await pgMeta.columns.remove(res.data!.id, { cascade: true }) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.stringMatching(/^\d+\.1$/), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "check": null, + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "id": StringMatching /\\^\\\\d\\+\\\\\\.1\\$/, + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": false, + "is_unique": false, + "is_updatable": true, + "name": "id", + "ordinal_position": 1, + "schema": "public", + "table": "t", + "table_id": Any, + }, + "error": null, + } + ` + ) + + res = await pgMeta.columns.retrieve({ + schema: 'public', + table: 't', + name: 't_id', + }) + expect(res).toMatchInlineSnapshot(` + { + "data": null, + "error": { + "message": "Cannot find a column named t_id in table public.t", + }, + } + `) + + await pgMeta.query(`drop table public.t;`) +}) + +test('column with multiple checks', async () => { + await pgMeta.query(`create table t(c int8 check (c != 0) check (c != -1))`) + + const res = await pgMeta.columns.list() + const columns = res.data + ?.filter((c) => c.schema === 'public' && c.table === 't') + .map(({ id, table_id, ...c }) => c) + expect(columns).toMatchInlineSnapshot(` + [ + { + "check": "c <> 0", + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + }, + ] + `) + + await pgMeta.query(`drop table t`) +}) + +test('column with multiple unique constraints', async () => { + await pgMeta.query(`create table t(c int8 unique); alter table t add unique (c);`) + + const res = await pgMeta.columns.list() + const columns = res.data + ?.filter((c) => c.schema === 'public' && c.table === 't') + .map(({ id, table_id, ...c }) => c) + expect(columns).toMatchInlineSnapshot(` + [ + { + "check": null, + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": true, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + }, + ] + `) + + await pgMeta.query(`drop table t`) +}) + +test('dropping column checks', async () => { + await pgMeta.query(`create table public.t(c int8 check (c != 0))`) + + let res = await pgMeta.columns.retrieve({ + schema: 'public', + table: 't', + name: 'c', + }) + res = await pgMeta.columns.update(res.data!.id, { check: null }) + expect(res?.data?.check).toMatchInlineSnapshot(`null`) + + await pgMeta.query(`drop table t`) +}) + +test('column with fully-qualified type', async () => { + await pgMeta.query(`create table public.t(); create schema s; create type s.my_type as enum ();`) + + const table = await pgMeta.tables.retrieve({ + schema: 'public', + name: 't', + }) + const { data } = await pgMeta.columns.create({ + table_id: table.data!.id, + name: 'c', + type: 's.my_type', + }) + const { id, table_id, ...column } = data! + expect(column).toMatchInlineSnapshot(` + { + "check": null, + "comment": null, + "data_type": "USER-DEFINED", + "default_value": null, + "enums": [], + "format": "my_type", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + } + `) + + await pgMeta.query(`drop table public.t; drop schema s cascade;`) +}) diff --git a/test/lib/config.ts b/test/lib/config.ts new file mode 100644 index 00000000..066139e5 --- /dev/null +++ b/test/lib/config.ts @@ -0,0 +1,29 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +test('list', async () => { + const res = await pgMeta.config.list() + expect(res.data?.find(({ name }) => name === 'autovacuum')).toMatchInlineSnapshot(` + { + "boot_val": "on", + "category": "Autovacuum", + "context": "sighup", + "enumvals": null, + "extra_desc": null, + "group": "Autovacuum", + "max_val": null, + "min_val": null, + "name": "autovacuum", + "pending_restart": false, + "reset_val": "on", + "setting": "on", + "short_desc": "Starts the autovacuum subprocess.", + "source": "default", + "sourcefile": null, + "sourceline": null, + "subgroup": "", + "unit": null, + "vartype": "bool", + } + `) +}) diff --git a/test/lib/extensions.ts b/test/lib/extensions.ts new file mode 100644 index 00000000..58615197 --- /dev/null +++ b/test/lib/extensions.ts @@ -0,0 +1,123 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +test('list', async () => { + const res = await pgMeta.extensions.list() + expect(res.data?.find(({ name }) => name === 'hstore')).toMatchInlineSnapshot( + { default_version: expect.stringMatching(/^\d+.\d+$/) }, + ` + { + "comment": "data type for storing sets of (key, value) pairs", + "default_version": StringMatching /\\^\\\\d\\+\\.\\\\d\\+\\$/, + "installed_version": null, + "name": "hstore", + "schema": null, + } + ` + ) +}) + +test('retrieve, create, update, delete', async () => { + const { data: testSchema } = await pgMeta.schemas.create({ name: 'extensions' }) + + let res = await pgMeta.extensions.create({ name: 'hstore', version: '1.4' }) + expect(res).toMatchInlineSnapshot( + { + data: { + default_version: expect.stringMatching(/^\d+.\d+$/), + }, + }, + ` + { + "data": { + "comment": "data type for storing sets of (key, value) pairs", + "default_version": StringMatching /\\^\\\\d\\+\\.\\\\d\\+\\$/, + "installed_version": "1.4", + "name": "hstore", + "schema": "public", + }, + "error": null, + } + ` + ) + res = await pgMeta.extensions.retrieve({ name: res.data!.name }) + expect(res).toMatchInlineSnapshot( + { + data: { + default_version: expect.stringMatching(/^\d+.\d+$/), + }, + }, + ` + { + "data": { + "comment": "data type for storing sets of (key, value) pairs", + "default_version": StringMatching /\\^\\\\d\\+\\.\\\\d\\+\\$/, + "installed_version": "1.4", + "name": "hstore", + "schema": "public", + }, + "error": null, + } + ` + ) + res = await pgMeta.extensions.update(res.data!.name, { update: true, schema: 'extensions' }) + expect(res).toMatchInlineSnapshot( + { + data: { + default_version: expect.stringMatching(/^\d+.\d+$/), + installed_version: expect.stringMatching(/^\d+.\d+$/), + }, + }, + ` + { + "data": { + "comment": "data type for storing sets of (key, value) pairs", + "default_version": StringMatching /\\^\\\\d\\+\\.\\\\d\\+\\$/, + "installed_version": StringMatching /\\^\\\\d\\+\\.\\\\d\\+\\$/, + "name": "hstore", + "schema": "extensions", + }, + "error": null, + } + ` + ) + res = await pgMeta.extensions.remove(res.data!.name) + expect(res).toMatchInlineSnapshot( + { + data: { + default_version: expect.stringMatching(/^\d+.\d+$/), + installed_version: expect.stringMatching(/^\d+.\d+$/), + }, + }, + ` + { + "data": { + "comment": "data type for storing sets of (key, value) pairs", + "default_version": StringMatching /\\^\\\\d\\+\\.\\\\d\\+\\$/, + "installed_version": StringMatching /\\^\\\\d\\+\\.\\\\d\\+\\$/, + "name": "hstore", + "schema": "extensions", + }, + "error": null, + } + ` + ) + res = await pgMeta.extensions.retrieve({ name: res.data!.name }) + expect(res).toMatchInlineSnapshot( + { data: { default_version: expect.stringMatching(/^\d+.\d+$/) } }, + ` + { + "data": { + "comment": "data type for storing sets of (key, value) pairs", + "default_version": StringMatching /\\^\\\\d\\+\\.\\\\d\\+\\$/, + "installed_version": null, + "name": "hstore", + "schema": null, + }, + "error": null, + } + ` + ) + + await pgMeta.schemas.remove(testSchema!.id) +}) diff --git a/test/lib/foreign-tables.ts b/test/lib/foreign-tables.ts new file mode 100644 index 00000000..6be2360f --- /dev/null +++ b/test/lib/foreign-tables.ts @@ -0,0 +1,175 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +const cleanNondetFromResponse = (x: T) => { + const { data, ...rest } = x as any + + const cleanNondetFromData = ({ id, columns, ...rest }: any) => { + const cleaned = rest + if (columns) { + cleaned.columns = columns.map(({ id, table_id, ...rest }: any) => rest) + } + return cleaned + } + + return { + data: Array.isArray(data) ? data.map(cleanNondetFromData) : cleanNondetFromData(data), + ...rest, + } as T +} + +test('list', async () => { + const res = await pgMeta.foreignTables.list() + expect(cleanNondetFromResponse(res).data?.find(({ name }) => name === 'foreign_table')) + .toMatchInlineSnapshot(` + { + "columns": [ + { + "check": null, + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": false, + "is_unique": false, + "is_updatable": true, + "name": "id", + "ordinal_position": 1, + "schema": "public", + "table": "foreign_table", + }, + { + "check": null, + "comment": null, + "data_type": "text", + "default_value": null, + "enums": [], + "format": "text", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "name", + "ordinal_position": 2, + "schema": "public", + "table": "foreign_table", + }, + { + "check": null, + "comment": null, + "data_type": "USER-DEFINED", + "default_value": null, + "enums": [ + "ACTIVE", + "INACTIVE", + ], + "format": "user_status", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "status", + "ordinal_position": 3, + "schema": "public", + "table": "foreign_table", + }, + ], + "comment": null, + "name": "foreign_table", + "schema": "public", + } + `) +}) + +test('list without columns', async () => { + const res = await pgMeta.foreignTables.list({ includeColumns: false }) + expect(cleanNondetFromResponse(res).data?.find(({ name }) => name === 'foreign_table')) + .toMatchInlineSnapshot(` + { + "comment": null, + "name": "foreign_table", + "schema": "public", + } + `) +}) + +test('retrieve', async () => { + const res = await pgMeta.foreignTables.retrieve({ schema: 'public', name: 'foreign_table' }) + expect(cleanNondetFromResponse(res)).toMatchInlineSnapshot(` + { + "data": { + "columns": [ + { + "check": null, + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": false, + "is_unique": false, + "is_updatable": true, + "name": "id", + "ordinal_position": 1, + "schema": "public", + "table": "foreign_table", + }, + { + "check": null, + "comment": null, + "data_type": "text", + "default_value": null, + "enums": [], + "format": "text", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "name", + "ordinal_position": 2, + "schema": "public", + "table": "foreign_table", + }, + { + "check": null, + "comment": null, + "data_type": "USER-DEFINED", + "default_value": null, + "enums": [ + "ACTIVE", + "INACTIVE", + ], + "format": "user_status", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "status", + "ordinal_position": 3, + "schema": "public", + "table": "foreign_table", + }, + ], + "comment": null, + "name": "foreign_table", + "schema": "public", + }, + "error": null, + } + `) +}) diff --git a/test/lib/functions.ts b/test/lib/functions.ts new file mode 100644 index 00000000..ce26e078 --- /dev/null +++ b/test/lib/functions.ts @@ -0,0 +1,533 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +test('list', async () => { + const res = await pgMeta.functions.list() + expect(res.data?.find(({ name }) => name === 'add')).toMatchInlineSnapshot( + { id: expect.any(Number) }, + ` + { + "args": [ + { + "has_default": false, + "mode": "in", + "name": "", + "type_id": 23, + }, + { + "has_default": false, + "mode": "in", + "name": "", + "type_id": 23, + }, + ], + "argument_types": "integer, integer", + "behavior": "IMMUTABLE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.add(integer, integer) + RETURNS integer + LANGUAGE sql + IMMUTABLE STRICT + AS $function$select $1 + $2;$function$ + ", + "config_params": null, + "definition": "select $1 + $2;", + "id": Any, + "identity_argument_types": "integer, integer", + "is_set_returning_function": false, + "language": "sql", + "name": "add", + "prorows": null, + "return_type": "integer", + "return_type_id": 23, + "return_type_relation_id": null, + "schema": "public", + "security_definer": false, + } + ` + ) +}) + +test('list set-returning function with single object limit', async () => { + const res = await pgMeta.functions.list() + expect(res.data?.filter(({ name }) => name === 'get_user_audit_setof_single_row')) + .toMatchInlineSnapshot(` + [ + { + "args": [ + { + "has_default": false, + "mode": "in", + "name": "user_row", + "type_id": 16395, + }, + ], + "argument_types": "user_row users", + "behavior": "STABLE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.get_user_audit_setof_single_row(user_row users) + RETURNS SETOF users_audit + LANGUAGE sql + STABLE ROWS 1 + AS $function$ + SELECT * FROM public.users_audit WHERE user_id = user_row.id; + $function$ + ", + "config_params": null, + "definition": " + SELECT * FROM public.users_audit WHERE user_id = user_row.id; + ", + "id": 16507, + "identity_argument_types": "user_row users", + "is_set_returning_function": true, + "language": "sql", + "name": "get_user_audit_setof_single_row", + "prorows": 1, + "return_type": "SETOF users_audit", + "return_type_id": 16419, + "return_type_relation_id": 16417, + "schema": "public", + "security_definer": false, + }, + ] + `) +}) + +test('list set-returning function with multiples definitions', async () => { + const res = await pgMeta.functions.list() + expect(res.data?.filter(({ name }) => name === 'get_todos_setof_rows')).toMatchInlineSnapshot(` + [ + { + "args": [ + { + "has_default": false, + "mode": "in", + "name": "user_row", + "type_id": 16395, + }, + ], + "argument_types": "user_row users", + "behavior": "STABLE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.get_todos_setof_rows(user_row users) + RETURNS SETOF todos + LANGUAGE sql + STABLE + AS $function$ + SELECT * FROM public.todos WHERE "user-id" = user_row.id; + $function$ + ", + "config_params": null, + "definition": " + SELECT * FROM public.todos WHERE "user-id" = user_row.id; + ", + "id": 16510, + "identity_argument_types": "user_row users", + "is_set_returning_function": true, + "language": "sql", + "name": "get_todos_setof_rows", + "prorows": 1000, + "return_type": "SETOF todos", + "return_type_id": 16405, + "return_type_relation_id": 16403, + "schema": "public", + "security_definer": false, + }, + { + "args": [ + { + "has_default": false, + "mode": "in", + "name": "todo_row", + "type_id": 16405, + }, + ], + "argument_types": "todo_row todos", + "behavior": "STABLE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.get_todos_setof_rows(todo_row todos) + RETURNS SETOF todos + LANGUAGE sql + STABLE + AS $function$ + SELECT * FROM public.todos WHERE "user-id" = todo_row."user-id"; + $function$ + ", + "config_params": null, + "definition": " + SELECT * FROM public.todos WHERE "user-id" = todo_row."user-id"; + ", + "id": 16511, + "identity_argument_types": "todo_row todos", + "is_set_returning_function": true, + "language": "sql", + "name": "get_todos_setof_rows", + "prorows": 1000, + "return_type": "SETOF todos", + "return_type_id": 16405, + "return_type_relation_id": 16403, + "schema": "public", + "security_definer": false, + }, + ] + `) +}) + +test('list functions with included schemas', async () => { + let res = await pgMeta.functions.list({ + includedSchemas: ['public'], + }) + + expect(res.data?.length).toBeGreaterThan(0) + + res.data?.forEach((func) => { + expect(func.schema).toBe('public') + }) +}) + +test('list functions with excluded schemas', async () => { + let res = await pgMeta.functions.list({ + excludedSchemas: ['public'], + }) + + res.data?.forEach((func) => { + expect(func.schema).not.toBe('public') + }) +}) + +test('list functions with excluded schemas and include System Schemas', async () => { + let res = await pgMeta.functions.list({ + excludedSchemas: ['public'], + includeSystemSchemas: true, + }) + + expect(res.data?.length).toBeGreaterThan(0) + + res.data?.forEach((func) => { + expect(func.schema).not.toBe('public') + }) +}) + +test('retrieve, create, update, delete', async () => { + const { + data: { id: testSchemaId }, + }: any = await pgMeta.schemas.create({ name: 'test_schema' }) + + let res = await pgMeta.functions.create({ + name: 'test_func', + schema: 'public', + args: ['a int2', 'b int2'], + definition: 'select a + b', + return_type: 'integer', + language: 'sql', + behavior: 'STABLE', + security_definer: true, + config_params: { search_path: 'hooks, auth', role: 'postgres' }, + }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "args": [ + { + "has_default": false, + "mode": "in", + "name": "a", + "type_id": 21, + }, + { + "has_default": false, + "mode": "in", + "name": "b", + "type_id": 21, + }, + ], + "argument_types": "a smallint, b smallint", + "behavior": "STABLE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.test_func(a smallint, b smallint) + RETURNS integer + LANGUAGE sql + STABLE SECURITY DEFINER + SET search_path TO 'hooks', 'auth' + SET role TO 'postgres' + AS $function$select a + b$function$ + ", + "config_params": { + "role": "postgres", + "search_path": "hooks, auth", + }, + "definition": "select a + b", + "id": Any, + "identity_argument_types": "a smallint, b smallint", + "is_set_returning_function": false, + "language": "sql", + "name": "test_func", + "prorows": null, + "return_type": "integer", + "return_type_id": 23, + "return_type_relation_id": null, + "schema": "public", + "security_definer": true, + }, + "error": null, + } + ` + ) + res = await pgMeta.functions.retrieve({ id: res.data!.id }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "args": [ + { + "has_default": false, + "mode": "in", + "name": "a", + "type_id": 21, + }, + { + "has_default": false, + "mode": "in", + "name": "b", + "type_id": 21, + }, + ], + "argument_types": "a smallint, b smallint", + "behavior": "STABLE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.test_func(a smallint, b smallint) + RETURNS integer + LANGUAGE sql + STABLE SECURITY DEFINER + SET search_path TO 'hooks', 'auth' + SET role TO 'postgres' + AS $function$select a + b$function$ + ", + "config_params": { + "role": "postgres", + "search_path": "hooks, auth", + }, + "definition": "select a + b", + "id": Any, + "identity_argument_types": "a smallint, b smallint", + "is_set_returning_function": false, + "language": "sql", + "name": "test_func", + "prorows": null, + "return_type": "integer", + "return_type_id": 23, + "return_type_relation_id": null, + "schema": "public", + "security_definer": true, + }, + "error": null, + } + ` + ) + res = await pgMeta.functions.update(res.data!.id, { + name: 'test_func_renamed', + schema: 'test_schema', + definition: 'select b - a', + }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "args": [ + { + "has_default": false, + "mode": "in", + "name": "a", + "type_id": 21, + }, + { + "has_default": false, + "mode": "in", + "name": "b", + "type_id": 21, + }, + ], + "argument_types": "a smallint, b smallint", + "behavior": "STABLE", + "complete_statement": "CREATE OR REPLACE FUNCTION test_schema.test_func_renamed(a smallint, b smallint) + RETURNS integer + LANGUAGE sql + STABLE SECURITY DEFINER + SET role TO 'postgres' + SET search_path TO 'hooks', 'auth' + AS $function$select b - a$function$ + ", + "config_params": { + "role": "postgres", + "search_path": "hooks, auth", + }, + "definition": "select b - a", + "id": Any, + "identity_argument_types": "a smallint, b smallint", + "is_set_returning_function": false, + "language": "sql", + "name": "test_func_renamed", + "prorows": null, + "return_type": "integer", + "return_type_id": 23, + "return_type_relation_id": null, + "schema": "test_schema", + "security_definer": true, + }, + "error": null, + } + ` + ) + res = await pgMeta.functions.remove(res.data!.id) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "args": [ + { + "has_default": false, + "mode": "in", + "name": "a", + "type_id": 21, + }, + { + "has_default": false, + "mode": "in", + "name": "b", + "type_id": 21, + }, + ], + "argument_types": "a smallint, b smallint", + "behavior": "STABLE", + "complete_statement": "CREATE OR REPLACE FUNCTION test_schema.test_func_renamed(a smallint, b smallint) + RETURNS integer + LANGUAGE sql + STABLE SECURITY DEFINER + SET role TO 'postgres' + SET search_path TO 'hooks', 'auth' + AS $function$select b - a$function$ + ", + "config_params": { + "role": "postgres", + "search_path": "hooks, auth", + }, + "definition": "select b - a", + "id": Any, + "identity_argument_types": "a smallint, b smallint", + "is_set_returning_function": false, + "language": "sql", + "name": "test_func_renamed", + "prorows": null, + "return_type": "integer", + "return_type_id": 23, + "return_type_relation_id": null, + "schema": "test_schema", + "security_definer": true, + }, + "error": null, + } + ` + ) + res = await pgMeta.functions.retrieve({ id: res.data!.id }) + expect(res).toMatchObject({ + data: null, + error: { + message: expect.stringMatching(/^Cannot find a function with ID \d+$/), + }, + }) + + await pgMeta.schemas.remove(testSchemaId) +}) + +test('retrieve set-returning function', async () => { + const res = await pgMeta.functions.retrieve({ + schema: 'public', + name: 'function_returning_set_of_rows', + args: [], + }) + expect(res.data).toMatchInlineSnapshot( + { + id: expect.any(Number), + return_type_id: expect.any(Number), + return_type_relation_id: expect.any(Number), + }, + ` + { + "args": [], + "argument_types": "", + "behavior": "STABLE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.function_returning_set_of_rows() + RETURNS SETOF users + LANGUAGE sql + STABLE + AS $function$ + select * from public.users; + $function$ + ", + "config_params": null, + "definition": " + select * from public.users; + ", + "id": Any, + "identity_argument_types": "", + "is_set_returning_function": true, + "language": "sql", + "name": "function_returning_set_of_rows", + "prorows": 1000, + "return_type": "SETOF users", + "return_type_id": Any, + "return_type_relation_id": Any, + "schema": "public", + "security_definer": false, + } + ` + ) +}) + +test('retrieve function by args filter - polymorphic function with text argument', async () => { + const res = await pgMeta.functions.retrieve({ + schema: 'public', + name: 'polymorphic_function', + args: ['text'], + }) + expect(res.data).toMatchObject({ + name: 'polymorphic_function', + schema: 'public', + argument_types: 'text', + args: [ + { type_id: 25, mode: 'in' }, // text type_id is 25 + ], + }) + expect(res.error).toBeNull() +}) + +test('retrieve function by args filter - polymorphic function with boolean argument', async () => { + const res = await pgMeta.functions.retrieve({ + schema: 'public', + name: 'polymorphic_function', + args: ['boolean'], + }) + expect(res.data).toMatchObject({ + name: 'polymorphic_function', + schema: 'public', + argument_types: 'boolean', + args: [ + { type_id: 16, mode: 'in' }, // boolean type_id is 16 + ], + }) + expect(res.error).toBeNull() +}) + +test('retrieve function by args filter - function with no arguments', async () => { + const res = await pgMeta.functions.retrieve({ + schema: 'public', + name: 'function_returning_set_of_rows', + args: [], + }) + expect(res.data).toMatchObject({ + name: 'function_returning_set_of_rows', + schema: 'public', + argument_types: '', + args: [], + }) + expect(res.error).toBeNull() +}) diff --git a/test/lib/policies.ts b/test/lib/policies.ts new file mode 100644 index 00000000..ef7eccbc --- /dev/null +++ b/test/lib/policies.ts @@ -0,0 +1,190 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +test('list', async () => { + const res = await pgMeta.policies.list() + expect(res.data?.find(({ name }) => name === 'categories_update_policy')).toMatchInlineSnapshot( + { id: expect.any(Number), table_id: expect.any(Number) }, + ` + { + "action": "PERMISSIVE", + "check": null, + "command": "UPDATE", + "definition": "(current_setting('my.username'::text) = name)", + "id": Any, + "name": "categories_update_policy", + "roles": [ + "postgres", + ], + "schema": "public", + "table": "category", + "table_id": Any, + } + ` + ) +}) + +test('list policies with included schemas', async () => { + let res = await pgMeta.policies.list({ + includedSchemas: ['public'], + }) + + expect(res.data?.length).toBeGreaterThan(0) + + res.data?.forEach((policy) => { + expect(policy.schema).toBe('public') + }) +}) + +test('list policies with excluded schemas', async () => { + let res = await pgMeta.policies.list({ + excludedSchemas: ['public'], + }) + + res.data?.forEach((policy) => { + expect(policy.schema).not.toBe('public') + }) +}) + +test('list policies with excluded schemas and include System Schemas', async () => { + let res = await pgMeta.policies.list({ + excludedSchemas: ['public'], + includeSystemSchemas: true, + }) + + res.data?.forEach((policy) => { + expect(policy.schema).not.toBe('public') + }) +}) + +test('retrieve, create, update, delete', async () => { + let res = await pgMeta.policies.create({ + name: 'test policy', + schema: 'public', + table: 'memes', + action: 'RESTRICTIVE', + }) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.any(Number), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "action": "RESTRICTIVE", + "check": null, + "command": "ALL", + "definition": null, + "id": Any, + "name": "test policy", + "roles": [ + "public", + ], + "schema": "public", + "table": "memes", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.policies.retrieve({ id: res.data!.id }) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.any(Number), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "action": "RESTRICTIVE", + "check": null, + "command": "ALL", + "definition": null, + "id": Any, + "name": "test policy", + "roles": [ + "public", + ], + "schema": "public", + "table": "memes", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.policies.update(res.data!.id, { + name: 'policy updated', + definition: "current_setting('my.username') IN (name)", + check: "current_setting('my.username') IN (name)", + roles: ['postgres'], + }) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.any(Number), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "action": "RESTRICTIVE", + "check": "(current_setting('my.username'::text) = name)", + "command": "ALL", + "definition": "(current_setting('my.username'::text) = name)", + "id": Any, + "name": "policy updated", + "roles": [ + "postgres", + ], + "schema": "public", + "table": "memes", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.policies.remove(res.data!.id) + expect(res).toMatchInlineSnapshot( + { + data: { + id: expect.any(Number), + table_id: expect.any(Number), + }, + }, + ` + { + "data": { + "action": "RESTRICTIVE", + "check": "(current_setting('my.username'::text) = name)", + "command": "ALL", + "definition": "(current_setting('my.username'::text) = name)", + "id": Any, + "name": "policy updated", + "roles": [ + "postgres", + ], + "schema": "public", + "table": "memes", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.policies.retrieve({ id: res.data!.id }) + expect(res).toMatchObject({ + data: null, + error: { + message: expect.stringMatching(/^Cannot find a policy with ID \d+$/), + }, + }) +}) diff --git a/test/lib/publications.ts b/test/lib/publications.ts new file mode 100644 index 00000000..1f1c973b --- /dev/null +++ b/test/lib/publications.ts @@ -0,0 +1,270 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +const cleanNondet = (x: any) => { + const { + data: { tables, ...rest2 }, + ...rest1 + } = x + + return { + data: { + tables: tables?.map(({ id, ...rest }: any) => rest) ?? tables, + ...rest2, + }, + ...rest1, + } +} + +test('retrieve, create, update, delete', async () => { + let res = await pgMeta.publications.create({ + name: 'a', + publish_insert: true, + publish_update: true, + publish_delete: true, + publish_truncate: false, + tables: ['users'], + }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "id": Any, + "name": "a", + "owner": "postgres", + "publish_delete": true, + "publish_insert": true, + "publish_truncate": false, + "publish_update": true, + "tables": [ + { + "name": "users", + "schema": "public", + }, + ], + }, + "error": null, + } + ` + ) + res = await pgMeta.publications.retrieve({ id: res.data!.id }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "id": Any, + "name": "a", + "owner": "postgres", + "publish_delete": true, + "publish_insert": true, + "publish_truncate": false, + "publish_update": true, + "tables": [ + { + "name": "users", + "schema": "public", + }, + ], + }, + "error": null, + } + ` + ) + res = await pgMeta.publications.update(res.data!.id, { + name: 'b', + publish_insert: false, + tables: [], + }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "id": Any, + "name": "b", + "owner": "postgres", + "publish_delete": true, + "publish_insert": false, + "publish_truncate": false, + "publish_update": true, + "tables": [], + }, + "error": null, + } + ` + ) + res = await pgMeta.publications.remove(res.data!.id) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "id": Any, + "name": "b", + "owner": "postgres", + "publish_delete": true, + "publish_insert": false, + "publish_truncate": false, + "publish_update": true, + "tables": [], + }, + "error": null, + } + ` + ) + res = await pgMeta.publications.retrieve({ id: res.data!.id }) + expect(res).toMatchObject({ + data: null, + error: { + message: expect.stringMatching(/^Cannot find a publication with ID \d+$/), + }, + }) +}) + +test('tables with uppercase', async () => { + const { + data: { id: testTableId }, + }: any = await pgMeta.tables.create({ name: 'T' }) + + let res = await pgMeta.publications.create({ + name: 'pub', + tables: ['T'], + }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "id": Any, + "name": "pub", + "owner": "postgres", + "publish_delete": false, + "publish_insert": false, + "publish_truncate": false, + "publish_update": false, + "tables": [ + { + "name": "T", + "schema": "public", + }, + ], + }, + "error": null, + } + ` + ) + res = await pgMeta.publications.update(res.data!.id, { + tables: ['T'], + }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "id": Any, + "name": "pub", + "owner": "postgres", + "publish_delete": false, + "publish_insert": false, + "publish_truncate": false, + "publish_update": false, + "tables": [ + { + "name": "T", + "schema": "public", + }, + ], + }, + "error": null, + } + ` + ) + await pgMeta.publications.remove(res.data!.id) + + await pgMeta.tables.remove(testTableId) +}) + +test('FOR ALL TABLES', async () => { + let res = await pgMeta.publications.create({ + name: 'for_all', + publish_insert: true, + publish_update: true, + publish_delete: true, + publish_truncate: false, + }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "id": Any, + "name": "for_all", + "owner": "postgres", + "publish_delete": true, + "publish_insert": true, + "publish_truncate": false, + "publish_update": true, + "tables": null, + }, + "error": null, + } + ` + ) + await pgMeta.publications.remove(res.data!.id) +}) + +test('update no tables -> all tables', async () => { + const { data } = await pgMeta.publications.create({ + name: 'pub', + tables: [], + }) + const res = await pgMeta.publications.update(data!.id, { tables: null }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "id": Any, + "name": "pub", + "owner": "postgres", + "publish_delete": false, + "publish_insert": false, + "publish_truncate": false, + "publish_update": false, + "tables": null, + }, + "error": null, + } + ` + ) + await pgMeta.publications.remove(res.data!.id) +}) + +test('update all tables -> no tables', async () => { + const { data } = await pgMeta.publications.create({ + name: 'pub', + tables: null, + }) + const res = await pgMeta.publications.update(data!.id, { tables: [] }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "id": Any, + "name": "pub", + "owner": "postgres", + "publish_delete": false, + "publish_insert": false, + "publish_truncate": false, + "publish_update": false, + "tables": [], + }, + "error": null, + } + ` + ) + await pgMeta.publications.remove(res.data!.id) +}) diff --git a/test/lib/roles.ts b/test/lib/roles.ts new file mode 100644 index 00000000..516a0871 --- /dev/null +++ b/test/lib/roles.ts @@ -0,0 +1,260 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +test('list', async () => { + const res = await pgMeta.roles.list() + + let role = res.data?.find(({ name }) => name === 'postgres') + + expect(role).toMatchInlineSnapshot( + { active_connections: expect.any(Number), id: expect.any(Number) }, + ` + { + "active_connections": Any, + "can_bypass_rls": true, + "can_create_db": true, + "can_create_role": true, + "can_login": true, + "config": null, + "connection_limit": 100, + "id": Any, + "inherit_role": true, + "is_replication_role": true, + "is_superuser": true, + "name": "postgres", + "password": "********", + "valid_until": null, + } + ` + ) + + // pg_monitor is a predefined role. `includeDefaultRoles` defaults to false, + // so it shouldn't be included in the result. + role = res.data?.find(({ name }) => name === 'pg_monitor') + + expect(role).toMatchInlineSnapshot(`undefined`) +}) + +test('list w/ default roles', async () => { + const res = await pgMeta.roles.list({ includeDefaultRoles: true }) + + const role = res.data?.find(({ name }) => name === 'pg_monitor') + + expect(role).toMatchInlineSnapshot( + { + active_connections: expect.any(Number), + id: expect.any(Number), + }, + ` + { + "active_connections": Any, + "can_bypass_rls": false, + "can_create_db": false, + "can_create_role": false, + "can_login": false, + "config": null, + "connection_limit": 100, + "id": Any, + "inherit_role": true, + "is_replication_role": false, + "is_superuser": false, + "name": "pg_monitor", + "password": "********", + "valid_until": null, + } + ` + ) +}) + +test('retrieve, create, update, delete', async () => { + let res = await pgMeta.roles.create({ + name: 'r', + is_superuser: true, + can_create_db: true, + can_create_role: true, + inherit_role: false, + can_login: true, + is_replication_role: true, + can_bypass_rls: true, + connection_limit: 100, + valid_until: '2020-01-01T00:00:00.000Z', + config: { search_path: 'extension, public' }, + }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "active_connections": 0, + "can_bypass_rls": true, + "can_create_db": true, + "can_create_role": true, + "can_login": true, + "config": { + "search_path": "extension, public", + }, + "connection_limit": 100, + "id": Any, + "inherit_role": false, + "is_replication_role": true, + "is_superuser": true, + "name": "r", + "password": "********", + "valid_until": "2020-01-01 00:00:00+00", + }, + "error": null, + } + ` + ) + res = await pgMeta.roles.retrieve({ id: res.data!.id }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "active_connections": 0, + "can_bypass_rls": true, + "can_create_db": true, + "can_create_role": true, + "can_login": true, + "config": { + "search_path": "extension, public", + }, + "connection_limit": 100, + "id": Any, + "inherit_role": false, + "is_replication_role": true, + "is_superuser": true, + "name": "r", + "password": "********", + "valid_until": "2020-01-01 00:00:00+00", + }, + "error": null, + } + ` + ) + await pgMeta.roles.remove(res.data!.id) + res = await pgMeta.roles.create({ + name: 'r', + }) + res = await pgMeta.roles.update(res.data!.id, { + name: 'rr', + is_superuser: true, + can_create_db: true, + can_create_role: true, + inherit_role: false, + can_login: true, + is_replication_role: true, + can_bypass_rls: true, + connection_limit: 100, + valid_until: '2020-01-01T00:00:00.000Z', + config: [ + { + op: 'replace', + path: 'search_path', + value: 'public', + }, + { + op: 'add', + path: 'log_statement', + value: 'all', + }, + ], + }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "active_connections": 0, + "can_bypass_rls": true, + "can_create_db": true, + "can_create_role": true, + "can_login": true, + "config": { + "log_statement": "all", + "search_path": "public", + }, + "connection_limit": 100, + "id": Any, + "inherit_role": false, + "is_replication_role": true, + "is_superuser": true, + "name": "rr", + "password": "********", + "valid_until": "2020-01-01 00:00:00+00", + }, + "error": null, + } + ` + ) + // Test remove config + res = await pgMeta.roles.update(res.data!.id, { + config: [ + { + op: 'remove', + path: 'log_statement', + }, + ], + }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "active_connections": 0, + "can_bypass_rls": true, + "can_create_db": true, + "can_create_role": true, + "can_login": true, + "config": { + "search_path": "public", + }, + "connection_limit": 100, + "id": Any, + "inherit_role": false, + "is_replication_role": true, + "is_superuser": true, + "name": "rr", + "password": "********", + "valid_until": "2020-01-01 00:00:00+00", + }, + "error": null, + } + ` + ) + res = await pgMeta.roles.remove(res.data!.id) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "active_connections": 0, + "can_bypass_rls": true, + "can_create_db": true, + "can_create_role": true, + "can_login": true, + "config": { + "search_path": "public", + }, + "connection_limit": 100, + "id": Any, + "inherit_role": false, + "is_replication_role": true, + "is_superuser": true, + "name": "rr", + "password": "********", + "valid_until": "2020-01-01 00:00:00+00", + }, + "error": null, + } + ` + ) + res = await pgMeta.roles.retrieve({ id: res.data!.id }) + expect(res).toMatchObject({ + data: null, + error: { + message: expect.stringMatching(/^Cannot find a role with ID \d+$/), + }, + }) +}) diff --git a/test/lib/schemas.ts b/test/lib/schemas.ts new file mode 100644 index 00000000..b9fb4a77 --- /dev/null +++ b/test/lib/schemas.ts @@ -0,0 +1,97 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +test('list with system schemas', async () => { + const res = await pgMeta.schemas.list({ includeSystemSchemas: true }) + expect(res.data?.find(({ name }) => name === 'pg_catalog')).toMatchInlineSnapshot( + { id: expect.any(Number) }, + ` + { + "id": Any, + "name": "pg_catalog", + "owner": "postgres", + } + ` + ) +}) + +test('list without system schemas', async () => { + const res = await pgMeta.schemas.list({ includeSystemSchemas: false }) + expect(res.data?.find(({ name }) => name === 'pg_catalog')).toMatchInlineSnapshot(`undefined`) + expect(res.data?.find(({ name }) => name === 'public')).toMatchInlineSnapshot( + { id: expect.any(Number) }, + ` + { + "id": Any, + "name": "public", + "owner": "postgres", + } + ` + ) +}) + +test('retrieve, create, update, delete', async () => { + let res = await pgMeta.schemas.create({ name: 's' }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "id": Any, + "name": "s", + "owner": "postgres", + }, + "error": null, + } + ` + ) + res = await pgMeta.schemas.retrieve({ id: res.data!.id }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "id": Any, + "name": "s", + "owner": "postgres", + }, + "error": null, + } + ` + ) + res = await pgMeta.schemas.update(res.data!.id, { name: 'ss', owner: 'postgres' }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "id": Any, + "name": "ss", + "owner": "postgres", + }, + "error": null, + } + ` + ) + res = await pgMeta.schemas.remove(res.data!.id) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "id": Any, + "name": "ss", + "owner": "postgres", + }, + "error": null, + } + ` + ) + res = await pgMeta.schemas.retrieve({ id: res.data!.id }) + expect(res).toMatchObject({ + data: null, + error: { + message: expect.stringMatching(/^Cannot find a schema with ID \d+$/), + }, + }) +}) diff --git a/test/lib/secrets.ts b/test/lib/secrets.ts new file mode 100644 index 00000000..d71c8afc --- /dev/null +++ b/test/lib/secrets.ts @@ -0,0 +1,59 @@ +import { readFile } from 'node:fs/promises' +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' +import { getSecret } from '../../src/lib/secrets' + +vi.mock('node:fs/promises', async (): Promise => { + const originalModule = + await vi.importActual('node:fs/promises') + const readFile = vi.fn() + return { ...originalModule, readFile } +}) + +describe('getSecret', () => { + const value = 'dummy' + + beforeEach(() => { + // Clears env var + vi.resetModules() + }) + + afterEach(() => { + delete process.env.SECRET + delete process.env.SECRET_FILE + }) + + test('loads from env', async () => { + process.env.SECRET = value + const res = await getSecret('SECRET') + expect(res).toBe(value) + }) + + test('loads from file', async () => { + process.env.SECRET_FILE = '/run/secrets/db_password' + vi.mocked(readFile).mockResolvedValueOnce(value) + const res = await getSecret('SECRET') + expect(res).toBe(value) + }) + + test('defaults to empty string', async () => { + expect(await getSecret('')).toBe('') + expect(await getSecret('SECRET')).toBe('') + }) + + test('default on file not found', async () => { + process.env.SECRET_FILE = '/run/secrets/db_password' + const e: NodeJS.ErrnoException = new Error('no such file or directory') + e.code = 'ENOENT' + vi.mocked(readFile).mockRejectedValueOnce(e) + const res = await getSecret('SECRET') + expect(res).toBe('') + }) + + test('throws on permission denied', async () => { + process.env.SECRET_FILE = '/run/secrets/db_password' + const e: NodeJS.ErrnoException = new Error('permission denied') + e.code = 'EACCES' + vi.mocked(readFile).mockRejectedValueOnce(e) + await expect(getSecret('SECRET')).rejects.toThrow() + }) +}) diff --git a/test/lib/tables.ts b/test/lib/tables.ts new file mode 100644 index 00000000..a0dfbaad --- /dev/null +++ b/test/lib/tables.ts @@ -0,0 +1,562 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +const cleanNondet = (x: any) => { + const { + data: { columns, primary_keys, relationships, ...rest2 }, + ...rest1 + } = x + + return { + data: { + columns: columns.map(({ id, table_id, ...rest }: any) => rest), + primary_keys: primary_keys.map(({ table_id, ...rest }: any) => rest), + relationships: relationships.map(({ id, ...rest }: any) => rest), + ...rest2, + }, + ...rest1, + } +} + +test('list', async () => { + const res = await pgMeta.tables.list() + + const { columns, primary_keys, relationships, ...rest }: any = res.data?.find( + ({ name }) => name === 'users' + ) + + expect({ + columns: columns.map(({ id, table_id, ...rest }: any) => rest), + primary_keys: primary_keys.map(({ table_id, ...rest }: any) => rest), + relationships: relationships.map(({ id, ...rest }: any) => rest), + ...rest, + }).toMatchInlineSnapshot( + { + bytes: expect.any(Number), + dead_rows_estimate: expect.any(Number), + id: expect.any(Number), + live_rows_estimate: expect.any(Number), + size: expect.any(String), + }, + ` + { + "bytes": Any, + "columns": [ + { + "check": null, + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "identity_generation": "BY DEFAULT", + "is_generated": false, + "is_identity": true, + "is_nullable": false, + "is_unique": false, + "is_updatable": true, + "name": "id", + "ordinal_position": 1, + "schema": "public", + "table": "users", + }, + { + "check": null, + "comment": null, + "data_type": "text", + "default_value": null, + "enums": [], + "format": "text", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "name", + "ordinal_position": 2, + "schema": "public", + "table": "users", + }, + { + "check": null, + "comment": null, + "data_type": "USER-DEFINED", + "default_value": "'ACTIVE'::user_status", + "enums": [ + "ACTIVE", + "INACTIVE", + ], + "format": "user_status", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "status", + "ordinal_position": 3, + "schema": "public", + "table": "users", + }, + { + "check": null, + "comment": null, + "data_type": "numeric", + "default_value": null, + "enums": [], + "format": "numeric", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "decimal", + "ordinal_position": 4, + "schema": "public", + "table": "users", + }, + { + "check": null, + "comment": null, + "data_type": "uuid", + "default_value": "gen_random_uuid()", + "enums": [], + "format": "uuid", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "user_uuid", + "ordinal_position": 5, + "schema": "public", + "table": "users", + }, + ], + "comment": null, + "dead_rows_estimate": Any, + "id": Any, + "live_rows_estimate": Any, + "name": "users", + "primary_keys": [ + { + "name": "id", + "schema": "public", + "table_name": "users", + }, + ], + "relationships": [ + { + "constraint_name": "todos_user-id_fkey", + "source_column_name": "user-id", + "source_schema": "public", + "source_table_name": "todos", + "target_column_name": "id", + "target_table_name": "users", + "target_table_schema": "public", + }, + { + "constraint_name": "user_details_user_id_fkey", + "source_column_name": "user_id", + "source_schema": "public", + "source_table_name": "user_details", + "target_column_name": "id", + "target_table_name": "users", + "target_table_schema": "public", + }, + ], + "replica_identity": "DEFAULT", + "rls_enabled": false, + "rls_forced": false, + "schema": "public", + "size": Any, + } + ` + ) +}) + +test('list without columns', async () => { + const res = await pgMeta.tables.list({ includeColumns: false }) + + const { columns, primary_keys, relationships, ...rest }: any = res.data?.find( + ({ name }) => name === 'users' + ) + + expect({ + primary_keys: primary_keys.map(({ table_id, ...rest }: any) => rest), + relationships: relationships.map(({ id, ...rest }: any) => rest), + ...rest, + }).toMatchInlineSnapshot( + { + bytes: expect.any(Number), + dead_rows_estimate: expect.any(Number), + id: expect.any(Number), + live_rows_estimate: expect.any(Number), + size: expect.any(String), + }, + ` + { + "bytes": Any, + "comment": null, + "dead_rows_estimate": Any, + "id": Any, + "live_rows_estimate": Any, + "name": "users", + "primary_keys": [ + { + "name": "id", + "schema": "public", + "table_name": "users", + }, + ], + "relationships": [ + { + "constraint_name": "todos_user-id_fkey", + "source_column_name": "user-id", + "source_schema": "public", + "source_table_name": "todos", + "target_column_name": "id", + "target_table_name": "users", + "target_table_schema": "public", + }, + { + "constraint_name": "user_details_user_id_fkey", + "source_column_name": "user_id", + "source_schema": "public", + "source_table_name": "user_details", + "target_column_name": "id", + "target_table_name": "users", + "target_table_schema": "public", + }, + ], + "replica_identity": "DEFAULT", + "rls_enabled": false, + "rls_forced": false, + "schema": "public", + "size": Any, + } + ` + ) +}) + +test('list tables with included schemas', async () => { + let res = await pgMeta.tables.list({ + includedSchemas: ['public'], + }) + + expect(res.data?.length).toBeGreaterThan(0) + + res.data?.forEach((table) => { + expect(table.schema).toBe('public') + }) +}) + +test('list tables with excluded schemas', async () => { + let res = await pgMeta.tables.list({ + excludedSchemas: ['public'], + }) + + res.data?.forEach((table) => { + expect(table.schema).not.toBe('public') + }) +}) + +test('list tables with excluded schemas and include System Schemas', async () => { + let res = await pgMeta.tables.list({ + excludedSchemas: ['public'], + includeSystemSchemas: true, + }) + + expect(res.data?.length).toBeGreaterThan(0) + + res.data?.forEach((table) => { + expect(table.schema).not.toBe('public') + }) +}) + +test('retrieve, create, update, delete', async () => { + let res = await pgMeta.tables.create({ name: 'test', comment: 'foo' }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "bytes": 0, + "columns": [], + "comment": "foo", + "dead_rows_estimate": 0, + "id": Any, + "live_rows_estimate": 0, + "name": "test", + "primary_keys": [], + "relationships": [], + "replica_identity": "DEFAULT", + "rls_enabled": false, + "rls_forced": false, + "schema": "public", + "size": "0 bytes", + }, + "error": null, + } + ` + ) + res = await pgMeta.tables.retrieve({ id: res.data!.id }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "bytes": 0, + "columns": [], + "comment": "foo", + "dead_rows_estimate": 0, + "id": Any, + "live_rows_estimate": 0, + "name": "test", + "primary_keys": [], + "relationships": [], + "replica_identity": "DEFAULT", + "rls_enabled": false, + "rls_forced": false, + "schema": "public", + "size": "0 bytes", + }, + "error": null, + } + ` + ) + res = await pgMeta.tables.update(res.data!.id, { + name: 'test a', + rls_enabled: true, + rls_forced: true, + replica_identity: 'NOTHING', + comment: 'foo', + }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "bytes": 0, + "columns": [], + "comment": "foo", + "dead_rows_estimate": 0, + "id": Any, + "live_rows_estimate": 0, + "name": "test a", + "primary_keys": [], + "relationships": [], + "replica_identity": "NOTHING", + "rls_enabled": true, + "rls_forced": true, + "schema": "public", + "size": "0 bytes", + }, + "error": null, + } + ` + ) + res = await pgMeta.tables.remove(res.data!.id) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "bytes": 0, + "columns": [], + "comment": "foo", + "dead_rows_estimate": 0, + "id": Any, + "live_rows_estimate": 0, + "name": "test a", + "primary_keys": [], + "relationships": [], + "replica_identity": "NOTHING", + "rls_enabled": true, + "rls_forced": true, + "schema": "public", + "size": "0 bytes", + }, + "error": null, + } + ` + ) + res = await pgMeta.tables.retrieve({ id: res.data!.id }) + expect(res).toMatchObject({ + data: null, + error: { + message: expect.stringMatching(/^Cannot find a table with ID \d+$/), + }, + }) +}) + +test('update with name unchanged', async () => { + let res = await pgMeta.tables.create({ name: 't' }) + res = await pgMeta.tables.update(res.data!.id, { + name: 't', + }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "bytes": 0, + "columns": [], + "comment": null, + "dead_rows_estimate": 0, + "id": Any, + "live_rows_estimate": 0, + "name": "t", + "primary_keys": [], + "relationships": [], + "replica_identity": "DEFAULT", + "rls_enabled": false, + "rls_forced": false, + "schema": "public", + "size": "0 bytes", + }, + "error": null, + } + ` + ) + await pgMeta.tables.remove(res.data!.id) +}) + +test("allow ' in comments", async () => { + let res = await pgMeta.tables.create({ name: 't', comment: "'" }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "bytes": 0, + "columns": [], + "comment": "'", + "dead_rows_estimate": 0, + "id": Any, + "live_rows_estimate": 0, + "name": "t", + "primary_keys": [], + "relationships": [], + "replica_identity": "DEFAULT", + "rls_enabled": false, + "rls_forced": false, + "schema": "public", + "size": "0 bytes", + }, + "error": null, + } + ` + ) + await pgMeta.tables.remove(res.data!.id) +}) + +test('primary keys', async () => { + let res = await pgMeta.tables.create({ name: 't' }) + await pgMeta.columns.create({ table_id: res.data!.id, name: 'c', type: 'int8' }) + await pgMeta.columns.create({ table_id: res.data!.id, name: 'cc', type: 'text' }) + res = await pgMeta.tables.update(res.data!.id, { + primary_keys: [{ name: 'c' }, { name: 'cc' }], + }) + expect(cleanNondet(res)).toMatchInlineSnapshot( + { + data: { + bytes: expect.any(Number), + dead_rows_estimate: expect.any(Number), + id: expect.any(Number), + live_rows_estimate: expect.any(Number), + size: expect.any(String), + }, + }, + ` + { + "data": { + "bytes": Any, + "columns": [ + { + "check": null, + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": false, + "is_unique": false, + "is_updatable": true, + "name": "c", + "ordinal_position": 1, + "schema": "public", + "table": "t", + }, + { + "check": null, + "comment": null, + "data_type": "text", + "default_value": null, + "enums": [], + "format": "text", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": false, + "is_unique": false, + "is_updatable": true, + "name": "cc", + "ordinal_position": 2, + "schema": "public", + "table": "t", + }, + ], + "comment": null, + "dead_rows_estimate": Any, + "id": Any, + "live_rows_estimate": Any, + "name": "t", + "primary_keys": [ + { + "name": "c", + "schema": "public", + "table_name": "t", + }, + { + "name": "cc", + "schema": "public", + "table_name": "t", + }, + ], + "relationships": [], + "replica_identity": "DEFAULT", + "rls_enabled": false, + "rls_forced": false, + "schema": "public", + "size": Any, + }, + "error": null, + } + ` + ) + await pgMeta.tables.remove(res.data!.id) +}) + +test('composite primary keys preserve order', async () => { + let res = await pgMeta.tables.create({ name: 't_pk_order' }) + await pgMeta.columns.create({ table_id: res.data!.id, name: 'col_a', type: 'int8' }) + await pgMeta.columns.create({ table_id: res.data!.id, name: 'col_b', type: 'text' }) + await pgMeta.columns.create({ table_id: res.data!.id, name: 'col_c', type: 'int4' }) + + // Set primary keys in specific order: col_c, col_a, col_b + res = await pgMeta.tables.update(res.data!.id, { + primary_keys: [{ name: 'col_c' }, { name: 'col_a' }, { name: 'col_b' }], + }) + + // Verify the order is preserved + expect(res.data!.primary_keys.map((pk: any) => pk.name)).toEqual(['col_c', 'col_a', 'col_b']) + + await pgMeta.tables.remove(res.data!.id) +}) diff --git a/test/lib/triggers.ts b/test/lib/triggers.ts new file mode 100644 index 00000000..beb5d63d --- /dev/null +++ b/test/lib/triggers.ts @@ -0,0 +1,314 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +test('retrieve, create, update, delete', async () => { + let res = await pgMeta.triggers.create({ + name: 'test_trigger', + schema: 'public', + table: 'users_audit', + function_schema: 'public', + function_name: 'audit_action', + function_args: ['test1', 'test2'], + activation: 'AFTER', + events: ['UPDATE'], + orientation: 'ROW', + condition: '(old.* IS DISTINCT FROM new.*)', + }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number), table_id: expect.any(Number) } }, + ` + { + "data": { + "activation": "AFTER", + "condition": "(old.* IS DISTINCT FROM new.*)", + "enabled_mode": "ORIGIN", + "events": [ + "UPDATE", + ], + "function_args": [ + "test1", + "test2", + ], + "function_name": "audit_action", + "function_schema": "public", + "id": Any, + "name": "test_trigger", + "orientation": "ROW", + "schema": "public", + "table": "users_audit", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.triggers.retrieve({ id: res.data!.id }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number), table_id: expect.any(Number) } }, + ` + { + "data": { + "activation": "AFTER", + "condition": "(old.* IS DISTINCT FROM new.*)", + "enabled_mode": "ORIGIN", + "events": [ + "UPDATE", + ], + "function_args": [ + "test1", + "test2", + ], + "function_name": "audit_action", + "function_schema": "public", + "id": Any, + "name": "test_trigger", + "orientation": "ROW", + "schema": "public", + "table": "users_audit", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.triggers.update(res.data!.id, { + name: 'test_trigger_renamed', + enabled_mode: 'DISABLED', + }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number), table_id: expect.any(Number) } }, + ` + { + "data": { + "activation": "AFTER", + "condition": "(old.* IS DISTINCT FROM new.*)", + "enabled_mode": "DISABLED", + "events": [ + "UPDATE", + ], + "function_args": [ + "test1", + "test2", + ], + "function_name": "audit_action", + "function_schema": "public", + "id": Any, + "name": "test_trigger_renamed", + "orientation": "ROW", + "schema": "public", + "table": "users_audit", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.triggers.update(res.data!.id, { + enabled_mode: 'REPLICA', + }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number), table_id: expect.any(Number) } }, + ` + { + "data": { + "activation": "AFTER", + "condition": "(old.* IS DISTINCT FROM new.*)", + "enabled_mode": "REPLICA", + "events": [ + "UPDATE", + ], + "function_args": [ + "test1", + "test2", + ], + "function_name": "audit_action", + "function_schema": "public", + "id": Any, + "name": "test_trigger_renamed", + "orientation": "ROW", + "schema": "public", + "table": "users_audit", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.triggers.remove(res.data!.id) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number), table_id: expect.any(Number) } }, + ` + { + "data": { + "activation": "AFTER", + "condition": "(old.* IS DISTINCT FROM new.*)", + "enabled_mode": "REPLICA", + "events": [ + "UPDATE", + ], + "function_args": [ + "test1", + "test2", + ], + "function_name": "audit_action", + "function_schema": "public", + "id": Any, + "name": "test_trigger_renamed", + "orientation": "ROW", + "schema": "public", + "table": "users_audit", + "table_id": Any, + }, + "error": null, + } + ` + ) + res = await pgMeta.triggers.retrieve({ id: res.data!.id }) + expect(res).toMatchObject({ + data: null, + error: { + message: expect.stringMatching(/^Cannot find a trigger with ID \d+$/), + }, + }) +}) + +test('multi event', async () => { + let res = await pgMeta.triggers.create({ + name: 'test_multi_event_trigger', + schema: 'public', + table: 'users_audit', + function_schema: 'public', + function_name: 'audit_action', + function_args: ['test1', 'test2'], + activation: 'AFTER', + events: ['insert', 'update', 'delete'], + orientation: 'ROW', + condition: '', + }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number), table_id: expect.any(Number) } }, + ` + { + "data": { + "activation": "AFTER", + "condition": null, + "enabled_mode": "ORIGIN", + "events": [ + "INSERT", + "DELETE", + "UPDATE", + ], + "function_args": [ + "test1", + "test2", + ], + "function_name": "audit_action", + "function_schema": "public", + "id": Any, + "name": "test_multi_event_trigger", + "orientation": "ROW", + "schema": "public", + "table": "users_audit", + "table_id": Any, + }, + "error": null, + } + ` + ) + await pgMeta.triggers.remove(res.data!.id) +}) + +test('triggers with the same name on different schemas', async () => { + await pgMeta.query(` +create function tr_f() returns trigger language plpgsql as 'begin end'; +create schema s1; create table s1.t(); create trigger tr before insert on s1.t execute function tr_f(); +create schema s2; create table s2.t(); create trigger tr before insert on s2.t execute function tr_f(); +`) + + const res = await pgMeta.triggers.list() + const triggers = res.data?.map(({ id, table_id, ...trigger }) => trigger) + expect(triggers).toMatchInlineSnapshot(` + [ + { + "activation": "BEFORE", + "condition": null, + "enabled_mode": "ORIGIN", + "events": [ + "INSERT", + ], + "function_args": [], + "function_name": "tr_f", + "function_schema": "public", + "name": "tr", + "orientation": "STATEMENT", + "schema": "s1", + "table": "t", + }, + { + "activation": "BEFORE", + "condition": null, + "enabled_mode": "ORIGIN", + "events": [ + "INSERT", + ], + "function_args": [], + "function_name": "tr_f", + "function_schema": "public", + "name": "tr", + "orientation": "STATEMENT", + "schema": "s2", + "table": "t", + }, + ] + `) + + await pgMeta.query('drop schema s1 cascade; drop schema s2 cascade;') +}) + +test('triggers on capitalized schema and table names', async () => { + await pgMeta.query(` +CREATE SCHEMA "MySchema"; +CREATE TABLE "MySchema"."MyTable" ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP +); +CREATE OR REPLACE FUNCTION "MySchema"."my_trigger_function"() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at := CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER "my_trigger" +BEFORE INSERT ON "MySchema"."MyTable" +FOR EACH ROW +EXECUTE FUNCTION "MySchema"."my_trigger_function"(); +`) + + const res = await pgMeta.triggers.list() + const triggers = res.data?.map(({ id, table_id, ...trigger }) => trigger) + expect(triggers).toMatchInlineSnapshot(` + [ + { + "activation": "BEFORE", + "condition": null, + "enabled_mode": "ORIGIN", + "events": [ + "INSERT", + ], + "function_args": [], + "function_name": "my_trigger_function", + "function_schema": "MySchema", + "name": "my_trigger", + "orientation": "ROW", + "schema": "MySchema", + "table": "MyTable", + }, + ] + `) + + await pgMeta.query('drop schema "MySchema" cascade;') +}) diff --git a/test/lib/types.ts b/test/lib/types.ts new file mode 100644 index 00000000..b256697e --- /dev/null +++ b/test/lib/types.ts @@ -0,0 +1,123 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +test('list', async () => { + const res = await pgMeta.types.list() + expect(res.data?.find(({ name }) => name === 'user_status')).toMatchInlineSnapshot( + { id: expect.any(Number) }, + ` + { + "attributes": [], + "comment": null, + "enums": [ + "ACTIVE", + "INACTIVE", + ], + "format": "user_status", + "id": Any, + "name": "user_status", + "schema": "public", + "type_relation_id": null, + } + ` + ) +}) + +test('list types with included schemas', async () => { + let res = await pgMeta.types.list({ + includedSchemas: ['public'], + }) + + expect(res.data?.length).toBeGreaterThan(0) + + res.data?.forEach((type) => { + expect(type.schema).toBe('public') + }) +}) + +test('list types with excluded schemas', async () => { + let res = await pgMeta.types.list({ + excludedSchemas: ['public'], + }) + + res.data?.forEach((type) => { + expect(type.schema).not.toBe('public') + }) +}) + +test('list types with excluded schemas and include System Schemas', async () => { + let res = await pgMeta.types.list({ + excludedSchemas: ['public'], + includeSystemSchemas: true, + }) + + expect(res.data?.length).toBeGreaterThan(0) + + res.data?.forEach((type) => { + expect(type.schema).not.toBe('public') + }) +}) + +test('list types with include Table Types', async () => { + const res = await pgMeta.types.list({ + includeTableTypes: true, + }) + + expect(res.data?.find(({ name }) => name === 'todos')).toMatchInlineSnapshot( + { id: expect.any(Number) }, + ` + { + "attributes": [], + "comment": null, + "enums": [], + "format": "todos", + "id": Any, + "name": "todos", + "schema": "public", + "type_relation_id": 16403, + } + ` + ) +}) + +test('list types without Table Types', async () => { + const res = await pgMeta.types.list({ + includeTableTypes: false, + }) + + res.data?.forEach((type) => { + expect(type.name).not.toBe('todos') + }) +}) + +test('composite type attributes', async () => { + await pgMeta.query(`create type test_composite as (id int8, data text);`) + + const res = await pgMeta.types.list() + expect(res.data?.find(({ name }) => name === 'test_composite')).toMatchInlineSnapshot( + { id: expect.any(Number), type_relation_id: expect.any(Number) }, + ` + { + "attributes": [ + { + "name": "id", + "type_id": 20, + }, + { + "name": "data", + "type_id": 25, + }, + ], + "comment": null, + "enums": [], + "format": "test_composite", + "id": Any, + "name": "test_composite", + "schema": "public", + "type_relation_id": Any, + } + ` + ) + + await pgMeta.query(`drop type test_composite;`) +}) diff --git a/test/lib/utils.ts b/test/lib/utils.ts new file mode 100644 index 00000000..a88391ed --- /dev/null +++ b/test/lib/utils.ts @@ -0,0 +1,11 @@ +import { afterAll } from 'vitest' +import { PostgresMeta } from '../../src/lib' + +export const TEST_CONNECTION_STRING = 'postgresql://postgres:postgres@localhost:5432' + +export const pgMeta = new PostgresMeta({ + max: 1, + connectionString: TEST_CONNECTION_STRING, +}) + +afterAll(() => pgMeta.end()) diff --git a/test/lib/version.ts b/test/lib/version.ts new file mode 100644 index 00000000..9309fe16 --- /dev/null +++ b/test/lib/version.ts @@ -0,0 +1,27 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +test('retrieve', async () => { + const res = await pgMeta.version.retrieve() + expect(res).toMatchInlineSnapshot( + { + data: { + active_connections: expect.any(Number), + max_connections: expect.any(Number), + version: expect.stringMatching(/^PostgreSQL/), + version_number: expect.any(Number), + }, + }, + ` + { + "data": { + "active_connections": Any, + "max_connections": Any, + "version": StringMatching /\\^PostgreSQL/, + "version_number": Any, + }, + "error": null, + } + ` + ) +}) diff --git a/test/lib/views.ts b/test/lib/views.ts new file mode 100644 index 00000000..e623e14b --- /dev/null +++ b/test/lib/views.ts @@ -0,0 +1,179 @@ +import { expect, test } from 'vitest' +import { pgMeta } from './utils' + +test('list', async () => { + const res = await pgMeta.views.list() + expect(res.data?.find(({ name }) => name === 'todos_view')).toMatchInlineSnapshot( + { id: expect.any(Number) }, + ` + { + "columns": [ + { + "check": null, + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "id": "16424.1", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "id", + "ordinal_position": 1, + "schema": "public", + "table": "todos_view", + "table_id": 16424, + }, + { + "check": null, + "comment": null, + "data_type": "text", + "default_value": null, + "enums": [], + "format": "text", + "id": "16424.2", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "details", + "ordinal_position": 2, + "schema": "public", + "table": "todos_view", + "table_id": 16424, + }, + { + "check": null, + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "id": "16424.3", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "user-id", + "ordinal_position": 3, + "schema": "public", + "table": "todos_view", + "table_id": 16424, + }, + ], + "comment": null, + "id": Any, + "is_updatable": true, + "name": "todos_view", + "schema": "public", + } + ` + ) +}) + +test('list without columns', async () => { + const res = await pgMeta.views.list({ includeColumns: false }) + expect(res.data?.find(({ name }) => name === 'todos_view')).toMatchInlineSnapshot( + { + id: expect.any(Number), + }, + ` + { + "comment": null, + "id": Any, + "is_updatable": true, + "name": "todos_view", + "schema": "public", + } + ` + ) +}) + +test('retrieve', async () => { + const res = await pgMeta.views.retrieve({ schema: 'public', name: 'todos_view' }) + expect(res).toMatchInlineSnapshot( + { data: { id: expect.any(Number) } }, + ` + { + "data": { + "columns": [ + { + "check": null, + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "id": "16424.1", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "id", + "ordinal_position": 1, + "schema": "public", + "table": "todos_view", + "table_id": 16424, + }, + { + "check": null, + "comment": null, + "data_type": "text", + "default_value": null, + "enums": [], + "format": "text", + "id": "16424.2", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "details", + "ordinal_position": 2, + "schema": "public", + "table": "todos_view", + "table_id": 16424, + }, + { + "check": null, + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "id": "16424.3", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "user-id", + "ordinal_position": 3, + "schema": "public", + "table": "todos_view", + "table_id": 16424, + }, + ], + "comment": null, + "id": Any, + "is_updatable": true, + "name": "todos_view", + "schema": "public", + }, + "error": null, + } + ` + ) +}) diff --git a/test/postgres/mnt/00-init.sql b/test/postgres/mnt/00-init.sql deleted file mode 100644 index 1c9f62dc..00000000 --- a/test/postgres/mnt/00-init.sql +++ /dev/null @@ -1,51 +0,0 @@ - - --- Tables for testing - -CREATE TYPE public.user_status AS ENUM ('ACTIVE', 'INACTIVE'); -CREATE TABLE public.users ( - id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - name text, - status user_status DEFAULT 'ACTIVE' -); -INSERT INTO - public.users (name) -VALUES - ('Joe Bloggs'), - ('Jane Doe'); - -CREATE TABLE public.todos ( - id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - details text, - "user-id" bigint REFERENCES users NOT NULL -); - -INSERT INTO - public.todos (details, "user-id") -VALUES - ('Star the repo', 1), - ('Watch the releases', 2); - - -CREATE FUNCTION add(integer, integer) RETURNS integer - AS 'select $1 + $2;' - LANGUAGE SQL - IMMUTABLE - RETURNS NULL ON NULL INPUT; - -create table public.users_audit ( - id BIGINT generated by DEFAULT as identity, - created_at timestamptz DEFAULT now(), - user_id bigint, - previous_value jsonb -); - -create function public.audit_action() -returns trigger as $$ -begin - insert into public.users_audit (user_id, previous_value) - values (old.id, row_to_json(old)); - - return new; -end; -$$ language plpgsql; diff --git a/test/publications.test.ts b/test/publications.test.ts new file mode 100644 index 00000000..0687c9dd --- /dev/null +++ b/test/publications.test.ts @@ -0,0 +1,187 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/publications', () => { + test('should list publications', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/publications', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list publications with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/publications?limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should return 404 for non-existent publication', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/publications/non-existent-publication', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) + + test('should create publication, retrieve, update, delete', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/publications', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_publication', + publish_insert: true, + publish_update: true, + publish_delete: true, + publish_truncate: false, + tables: ['users'], + }, + }) + expect(response.statusCode).toBe(200) + const responseData = response.json() + expect(responseData).toMatchObject({ + id: expect.any(Number), + name: 'test_publication', + owner: 'postgres', + publish_delete: true, + publish_insert: true, + publish_truncate: false, + publish_update: true, + tables: [ + { + id: expect.any(Number), + name: 'users', + schema: 'public', + }, + ], + }) + + const { id } = responseData + + const retrieveResponse = await app.inject({ + method: 'GET', + url: `/publications/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(retrieveResponse.statusCode).toBe(200) + const retrieveData = retrieveResponse.json() + expect(retrieveData).toMatchObject({ + id: expect.any(Number), + name: 'test_publication', + owner: 'postgres', + publish_delete: true, + publish_insert: true, + publish_truncate: false, + publish_update: true, + tables: [ + { + id: expect.any(Number), + name: 'users', + schema: 'public', + }, + ], + }) + + const updateResponse = await app.inject({ + method: 'PATCH', + url: `/publications/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + publish_delete: false, + }, + }) + expect(updateResponse.statusCode).toBe(200) + const updateData = updateResponse.json() + expect(updateData).toMatchObject({ + id: expect.any(Number), + name: 'test_publication', + owner: 'postgres', + publish_delete: false, + publish_insert: true, + publish_truncate: false, + publish_update: true, + tables: [ + { + id: expect.any(Number), + name: 'users', + schema: 'public', + }, + ], + }) + + const deleteResponse = await app.inject({ + method: 'DELETE', + url: `/publications/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(deleteResponse.statusCode).toBe(200) + const deleteData = deleteResponse.json() + expect(deleteData).toMatchObject({ + id: expect.any(Number), + name: 'test_publication', + owner: 'postgres', + publish_delete: false, + publish_insert: true, + publish_truncate: false, + publish_update: true, + tables: [ + { + id: expect.any(Number), + name: 'users', + schema: 'public', + }, + ], + }) + }) + + test('should return 400 for invalid payload', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/publications', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_publication', + tables: ['non_existent_table'], + }, + }) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "relation "non_existent_table" does not exist", + } + `) + }) +}) diff --git a/test/roles.test.ts b/test/roles.test.ts new file mode 100644 index 00000000..77b98c06 --- /dev/null +++ b/test/roles.test.ts @@ -0,0 +1,181 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/roles', () => { + test('should list roles', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/roles', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list roles with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/roles?limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should return 404 for non-existent role', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/roles/non-existent-role', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) + + test('should create role, retrieve, update, delete', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/roles', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_role', + }, + }) + expect(response.statusCode).toBe(200) + const responseData = response.json() + expect(responseData).toMatchObject({ + active_connections: 0, + can_bypass_rls: false, + can_create_db: false, + can_create_role: false, + can_login: false, + config: null, + connection_limit: 100, + id: expect.any(Number), + inherit_role: true, + is_replication_role: false, + is_superuser: false, + name: 'test_role', + password: '********', + valid_until: null, + }) + + const { id } = responseData + + const retrieveResponse = await app.inject({ + method: 'GET', + url: `/roles/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(retrieveResponse.statusCode).toBe(200) + const retrieveData = retrieveResponse.json() + expect(retrieveData).toMatchObject({ + active_connections: 0, + can_bypass_rls: false, + can_create_db: false, + can_create_role: false, + can_login: false, + config: null, + connection_limit: 100, + id: expect.any(Number), + inherit_role: true, + is_replication_role: false, + is_superuser: false, + name: 'test_role', + password: '********', + valid_until: null, + }) + + const updateResponse = await app.inject({ + method: 'PATCH', + url: `/roles/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_role_updated', + }, + }) + expect(updateResponse.statusCode).toBe(200) + const updateData = updateResponse.json() + expect(updateData).toMatchObject({ + active_connections: 0, + can_bypass_rls: false, + can_create_db: false, + can_create_role: false, + can_login: false, + config: null, + connection_limit: 100, + id: expect.any(Number), + inherit_role: true, + is_replication_role: false, + is_superuser: false, + name: 'test_role_updated', + password: '********', + valid_until: null, + }) + + const deleteResponse = await app.inject({ + method: 'DELETE', + url: `/roles/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(deleteResponse.statusCode).toBe(200) + const deleteData = deleteResponse.json() + expect(deleteData).toMatchObject({ + active_connections: 0, + can_bypass_rls: false, + can_create_db: false, + can_create_role: false, + can_login: false, + config: null, + connection_limit: 100, + id: expect.any(Number), + inherit_role: true, + is_replication_role: false, + is_superuser: false, + name: 'test_role_updated', + password: '********', + valid_until: null, + }) + }) + + test('should return 400 for invalid payload', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/roles', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'pg_', + }, + }) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "role name "pg_" is reserved", + } + `) + }) +}) diff --git a/test/schemas.test.ts b/test/schemas.test.ts new file mode 100644 index 00000000..73ed73c1 --- /dev/null +++ b/test/schemas.test.ts @@ -0,0 +1,137 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/schemas', () => { + test('should list schemas', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/schemas', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list schemas with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/schemas?include_system_schemas=true&limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should return 404 for non-existent schema', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/schemas/non-existent-schema', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) + + test('should create schema, retrieve, update, delete', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/schemas', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_schema', + }, + }) + expect(response.statusCode).toBe(200) + const responseData = response.json() + expect(responseData).toMatchObject({ + id: expect.any(Number), + name: 'test_schema', + owner: 'postgres', + }) + + const { id } = responseData + + const retrieveResponse = await app.inject({ + method: 'GET', + url: `/schemas/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(retrieveResponse.statusCode).toBe(200) + const retrieveData = retrieveResponse.json() + expect(retrieveData).toMatchObject({ + id: expect.any(Number), + name: 'test_schema', + owner: 'postgres', + }) + + const updateResponse = await app.inject({ + method: 'PATCH', + url: `/schemas/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_schema_updated', + }, + }) + expect(updateResponse.statusCode).toBe(200) + const updateData = updateResponse.json() + expect(updateData).toMatchObject({ + id: expect.any(Number), + name: 'test_schema_updated', + owner: 'postgres', + }) + + const deleteResponse = await app.inject({ + method: 'DELETE', + url: `/schemas/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(deleteResponse.statusCode).toBe(200) + const deleteData = deleteResponse.json() + expect(deleteData).toMatchObject({ + id: expect.any(Number), + name: 'test_schema_updated', + owner: 'postgres', + }) + }) + + test('should return 400 for invalid payload', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/schemas', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'pg_', + }, + }) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "unacceptable schema name "pg_"", + } + `) + }) +}) diff --git a/test/server/column-privileges.ts b/test/server/column-privileges.ts new file mode 100644 index 00000000..5237f264 --- /dev/null +++ b/test/server/column-privileges.ts @@ -0,0 +1,365 @@ +import { expect, test } from 'vitest' +import { PostgresColumnPrivileges } from '../../src/lib/types' +import { app } from './utils' + +test('list column privileges', async () => { + const res = await app.inject({ method: 'GET', path: '/column-privileges' }) + const column = res + .json() + .find( + ({ relation_schema, relation_name, column_name }) => + relation_schema === 'public' && relation_name === 'todos' && column_name === 'id' + )! + // We don't guarantee order of privileges, but we want to keep the snapshots consistent. + column.privileges.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))) + expect(column).toMatchInlineSnapshot( + { column_id: expect.stringMatching(/^\d+\.\d+$/) }, + ` + { + "column_id": StringMatching /\\^\\\\d\\+\\\\\\.\\\\d\\+\\$/, + "column_name": "id", + "privileges": [ + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "INSERT", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "REFERENCES", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "SELECT", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "UPDATE", + }, + ], + "relation_name": "todos", + "relation_schema": "public", + } + ` + ) +}) + +test('revoke & grant column privileges', async () => { + let res = await app.inject({ method: 'GET', path: '/column-privileges' }) + const { column_id } = res + .json() + .find( + ({ relation_schema, relation_name, column_name }) => + relation_schema === 'public' && relation_name === 'todos' && column_name === 'id' + )! + + res = await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: `create role r;`, + }, + }) + if (res.json().error) { + throw new Error(res.payload) + } + + res = await app.inject({ + method: 'POST', + path: '/column-privileges', + payload: [ + { + column_id, + grantee: 'r', + privilege_type: 'ALL', + }, + ], + }) + let privs = res.json() + expect(privs.length).toBe(1) + expect(privs[0]).toMatchInlineSnapshot( + { column_id: expect.stringMatching(/^\d+\.\d+$/) }, + ` + { + "column_id": StringMatching /\\^\\\\d\\+\\\\\\.\\\\d\\+\\$/, + "column_name": "id", + "privileges": [ + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "UPDATE", + }, + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "SELECT", + }, + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "REFERENCES", + }, + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "INSERT", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "UPDATE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "SELECT", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "REFERENCES", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "INSERT", + }, + ], + "relation_name": "todos", + "relation_schema": "public", + } + ` + ) + + res = await app.inject({ + method: 'DELETE', + path: '/column-privileges', + payload: [ + { + column_id, + grantee: 'r', + privilege_type: 'ALL', + }, + ], + }) + privs = res.json() + expect(privs.length).toBe(1) + expect(privs[0]).toMatchInlineSnapshot( + { column_id: expect.stringMatching(/^\d+\.\d+$/) }, + ` + { + "column_id": StringMatching /\\^\\\\d\\+\\\\\\.\\\\d\\+\\$/, + "column_name": "id", + "privileges": [ + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "UPDATE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "SELECT", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "REFERENCES", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "INSERT", + }, + ], + "relation_name": "todos", + "relation_schema": "public", + } + ` + ) + + res = await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: `drop role r;`, + }, + }) + if (res.json().error) { + throw new Error(res.payload) + } +}) + +test('revoke & grant column privileges w/ quoted column name', async () => { + let res = await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: `create role r; create table "t 1"("c 1" int8);`, + }, + }) + if (res.json().error) { + throw new Error(res.payload) + } + + res = await app.inject({ method: 'GET', path: '/column-privileges' }) + const { column_id } = res + .json() + .find(({ relation_name, column_name }) => relation_name === 't 1' && column_name === 'c 1')! + + res = await app.inject({ + method: 'POST', + path: '/column-privileges', + payload: [ + { + column_id, + grantee: 'r', + privilege_type: 'ALL', + }, + ], + }) + let privs = res.json() + expect(privs.length).toBe(1) + expect(privs[0]).toMatchInlineSnapshot( + { column_id: expect.stringMatching(/^\d+\.\d+$/) }, + ` + { + "column_id": StringMatching /\\^\\\\d\\+\\\\\\.\\\\d\\+\\$/, + "column_name": "c 1", + "privileges": [ + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "UPDATE", + }, + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "SELECT", + }, + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "REFERENCES", + }, + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "INSERT", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "UPDATE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "SELECT", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "REFERENCES", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "INSERT", + }, + ], + "relation_name": "t 1", + "relation_schema": "public", + } + ` + ) + + res = await app.inject({ + method: 'DELETE', + path: '/column-privileges', + payload: [ + { + column_id, + grantee: 'r', + privilege_type: 'ALL', + }, + ], + }) + privs = res.json() + expect(privs.length).toBe(1) + expect(privs[0]).toMatchInlineSnapshot( + { column_id: expect.stringMatching(/^\d+\.\d+$/) }, + ` + { + "column_id": StringMatching /\\^\\\\d\\+\\\\\\.\\\\d\\+\\$/, + "column_name": "c 1", + "privileges": [ + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "UPDATE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "SELECT", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "REFERENCES", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "INSERT", + }, + ], + "relation_name": "t 1", + "relation_schema": "public", + } + ` + ) + + res = await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: `drop role r; drop table "t 1";`, + }, + }) + if (res.json().error) { + throw new Error(res.payload) + } +}) diff --git a/test/server/indexes.ts b/test/server/indexes.ts new file mode 100644 index 00000000..1ad4d0a2 --- /dev/null +++ b/test/server/indexes.ts @@ -0,0 +1,106 @@ +import { expect, test } from 'vitest' +import { PostgresIndex } from '../../src/lib/types' +import { app } from './utils' + +test('list indexes', async () => { + const res = await app.inject({ method: 'GET', path: '/indexes' }) + const index = res + .json() + .find( + ({ index_definition }) => + index_definition === 'CREATE UNIQUE INDEX users_pkey ON public.users USING btree (id)' + )! + expect(index).toMatchInlineSnapshot( + ` + { + "access_method": "btree", + "check_xmin": false, + "class": [ + 3124, + ], + "collation": [ + 0, + ], + "comment": null, + "id": 16400, + "index_attributes": [ + { + "attribute_name": "id", + "attribute_number": 1, + "data_type": "bigint", + }, + ], + "index_definition": "CREATE UNIQUE INDEX users_pkey ON public.users USING btree (id)", + "index_predicate": null, + "is_clustered": false, + "is_exclusion": false, + "is_immediate": true, + "is_live": true, + "is_primary": true, + "is_ready": true, + "is_replica_identity": false, + "is_unique": true, + "is_valid": true, + "key_attributes": [ + 1, + ], + "number_of_attributes": 1, + "number_of_key_attributes": 1, + "options": [ + 0, + ], + "schema": "public", + "table_id": 16393, + } + ` + ) +}) + +test('retrieve index', async () => { + const res = await app.inject({ method: 'GET', path: '/indexes/16400' }) + const index = res.json() + expect(index).toMatchInlineSnapshot( + ` + { + "access_method": "btree", + "check_xmin": false, + "class": [ + 3124, + ], + "collation": [ + 0, + ], + "comment": null, + "id": 16400, + "index_attributes": [ + { + "attribute_name": "id", + "attribute_number": 1, + "data_type": "bigint", + }, + ], + "index_definition": "CREATE UNIQUE INDEX users_pkey ON public.users USING btree (id)", + "index_predicate": null, + "is_clustered": false, + "is_exclusion": false, + "is_immediate": true, + "is_live": true, + "is_primary": true, + "is_ready": true, + "is_replica_identity": false, + "is_unique": true, + "is_valid": true, + "key_attributes": [ + 1, + ], + "number_of_attributes": 1, + "number_of_key_attributes": 1, + "options": [ + 0, + ], + "schema": "public", + "table_id": 16393, + } + ` + ) +}) diff --git a/test/server/materialized-views.ts b/test/server/materialized-views.ts new file mode 100644 index 00000000..8799a6a9 --- /dev/null +++ b/test/server/materialized-views.ts @@ -0,0 +1,102 @@ +import { expect, test } from 'vitest' +import { app } from './utils' + +const cleanNondetFromBody = (x: T) => { + const cleanNondet = ({ id, columns, ...rest }: any) => { + const cleaned = rest + if (columns) { + cleaned.columns = columns.map(({ id, table_id, ...rest }: any) => rest) + } + return cleaned + } + + return (Array.isArray(x) ? x.map(cleanNondet) : cleanNondet(x)) as T +} + +test('materialized views', async () => { + const { body } = await app.inject({ method: 'GET', path: '/materialized-views' }) + expect(cleanNondetFromBody(JSON.parse(body))).toMatchInlineSnapshot(` + [ + { + "comment": null, + "is_populated": true, + "name": "todos_matview", + "schema": "public", + }, + ] + `) +}) + +test('materialized views with columns', async () => { + const { body } = await app.inject({ + method: 'GET', + path: '/materialized-views', + query: { include_columns: 'true' }, + }) + expect(cleanNondetFromBody(JSON.parse(body))).toMatchInlineSnapshot(` + [ + { + "columns": [ + { + "check": null, + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": false, + "name": "id", + "ordinal_position": 1, + "schema": "public", + "table": "todos_matview", + }, + { + "check": null, + "comment": null, + "data_type": "text", + "default_value": null, + "enums": [], + "format": "text", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": false, + "name": "details", + "ordinal_position": 2, + "schema": "public", + "table": "todos_matview", + }, + { + "check": null, + "comment": null, + "data_type": "bigint", + "default_value": null, + "enums": [], + "format": "int8", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": false, + "name": "user-id", + "ordinal_position": 3, + "schema": "public", + "table": "todos_matview", + }, + ], + "comment": null, + "is_populated": true, + "name": "todos_matview", + "schema": "public", + }, + ] + `) +}) diff --git a/test/server/query-timeout.ts b/test/server/query-timeout.ts new file mode 100644 index 00000000..47554afc --- /dev/null +++ b/test/server/query-timeout.ts @@ -0,0 +1,73 @@ +import { expect, test, describe } from 'vitest' +import { app } from './utils' +import { pgMeta } from '../lib/utils' + +const TIMEOUT = (Number(process.env.PG_QUERY_TIMEOUT_SECS) ?? 10) + 2 +const STATEMENT_TIMEOUT = (Number(process.env.PG_QUERY_TIMEOUT_SECS) ?? 10) + 1 + +describe('test query timeout', () => { + test( + `query timeout after ${TIMEOUT}s and connection cleanup`, + async () => { + const query = `SELECT pg_sleep(${TIMEOUT + 10});` + // Execute a query that will sleep for 10 seconds + const res = await app.inject({ + method: 'POST', + path: '/query', + query: `statementTimeoutSecs=${STATEMENT_TIMEOUT}`, + payload: { + query, + }, + }) + + // Check that we get the proper timeout error response + expect(res.statusCode).toBe(408) // Request Timeout + expect(res.json()).toMatchObject({ + error: expect.stringContaining('Query read timeout'), + }) + // wait one second for the statement timeout to take effect + await new Promise((resolve) => setTimeout(resolve, 1000)) + + // Verify that the connection has been cleaned up by checking active connections + const connectionsRes = await pgMeta.query(` + SELECT * FROM pg_stat_activity where application_name = 'postgres-meta 0.0.0-automated' and query ILIKE '%${query}%'; + `) + + // Should have no active connections except for our current query + expect(connectionsRes.data).toHaveLength(0) + }, + TIMEOUT * 1000 + ) + + test( + 'query without timeout parameter should not have timeout', + async () => { + const query = `SELECT pg_sleep(${TIMEOUT + 10});` + // Execute a query that will sleep for 10 seconds without specifying timeout + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { + query, + }, + }) + + // Check that we get the proper timeout error response + expect(res.statusCode).toBe(408) // Request Timeout + expect(res.json()).toMatchObject({ + error: expect.stringContaining('Query read timeout'), + }) + // wait one second + await new Promise((resolve) => setTimeout(resolve, 1000)) + + // Verify that the connection has not been cleaned up sinice there is no statementTimetout + const connectionsRes = await pgMeta.query(` + SELECT * FROM pg_stat_activity where application_name = 'postgres-meta 0.0.0-automated' and query ILIKE '%${query}%'; + `) + + // Should have no active connections except for our current query + expect(connectionsRes.data).toHaveLength(1) + }, + TIMEOUT * 1000 + ) +}) diff --git a/test/server/query.ts b/test/server/query.ts new file mode 100644 index 00000000..01f9cc92 --- /dev/null +++ b/test/server/query.ts @@ -0,0 +1,845 @@ +import { expect, test } from 'vitest' +import { app, normalizeUuids } from './utils' + +test('query', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: 'SELECT * FROM users' }, + }) + expect(normalizeUuids(res.json())).toMatchInlineSnapshot(` + [ + { + "decimal": null, + "id": 1, + "name": "Joe Bloggs", + "status": "ACTIVE", + "user_uuid": "00000000-0000-0000-0000-000000000000", + }, + { + "decimal": null, + "id": 2, + "name": "Jane Doe", + "status": "ACTIVE", + "user_uuid": "00000000-0000-0000-0000-000000000000", + }, + ] + `) +}) + +test('error', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: 'DROP TABLE missing_table' }, + }) + expect(res.json()).toMatchInlineSnapshot(` + { + "code": "42P01", + "error": "ERROR: 42P01: table "missing_table" does not exist + ", + "file": "tablecmds.c", + "formattedError": "ERROR: 42P01: table "missing_table" does not exist + ", + "length": 108, + "line": "1259", + "message": "table "missing_table" does not exist", + "name": "error", + "routine": "DropErrorMsgNonExistent", + "severity": "ERROR", + } + `) +}) + +test('parser select statements', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query/parse', + payload: { query: 'SELECT id, name FROM users where user_id = 1234' }, + }) + expect(res.json()).toMatchInlineSnapshot(` + { + "stmts": [ + { + "stmt": { + "SelectStmt": { + "fromClause": [ + { + "RangeVar": { + "inh": true, + "location": 21, + "relname": "users", + "relpersistence": "p", + }, + }, + ], + "limitOption": "LIMIT_OPTION_DEFAULT", + "op": "SETOP_NONE", + "targetList": [ + { + "ResTarget": { + "location": 7, + "val": { + "ColumnRef": { + "fields": [ + { + "String": { + "sval": "id", + }, + }, + ], + "location": 7, + }, + }, + }, + }, + { + "ResTarget": { + "location": 11, + "val": { + "ColumnRef": { + "fields": [ + { + "String": { + "sval": "name", + }, + }, + ], + "location": 11, + }, + }, + }, + }, + ], + "whereClause": { + "A_Expr": { + "kind": "AEXPR_OP", + "lexpr": { + "ColumnRef": { + "fields": [ + { + "String": { + "sval": "user_id", + }, + }, + ], + "location": 33, + }, + }, + "location": 41, + "name": [ + { + "String": { + "sval": "=", + }, + }, + ], + "rexpr": { + "A_Const": { + "ival": { + "ival": 1234, + }, + "location": 43, + }, + }, + }, + }, + }, + }, + }, + ], + "version": 170004, + } + `) +}) + +test('parser comments', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query/parse', + payload: { + query: ` +-- test comments +`, + }, + }) + expect(res.json()).toMatchInlineSnapshot(` + { + "stmts": [], + "version": 170004, + } + `) +}) + +test('parser create schema', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query/parse', + payload: { + query: ` +create schema if not exists test_schema; +`, + }, + }) + expect(res.json()).toMatchInlineSnapshot(` + { + "stmts": [ + { + "stmt": { + "CreateSchemaStmt": { + "if_not_exists": true, + "schemaname": "test_schema", + }, + }, + "stmt_len": 40, + }, + ], + "version": 170004, + } + `) +}) + +test('parser create statements', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query/parse', + payload: { + query: ` +CREATE TABLE table_name ( + id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + inserted_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL, + updated_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL, + data jsonb, + name text +); +`, + }, + }) + expect(res.json()).toMatchInlineSnapshot(` + { + "stmts": [ + { + "stmt": { + "CreateStmt": { + "oncommit": "ONCOMMIT_NOOP", + "relation": { + "inh": true, + "location": 14, + "relname": "table_name", + "relpersistence": "p", + }, + "tableElts": [ + { + "ColumnDef": { + "colname": "id", + "constraints": [ + { + "Constraint": { + "contype": "CONSTR_IDENTITY", + "generated_when": "d", + "location": 39, + }, + }, + { + "Constraint": { + "contype": "CONSTR_PRIMARY", + "location": 72, + }, + }, + ], + "is_local": true, + "location": 29, + "typeName": { + "location": 32, + "names": [ + { + "String": { + "sval": "pg_catalog", + }, + }, + { + "String": { + "sval": "int8", + }, + }, + ], + "typemod": -1, + }, + }, + }, + { + "ColumnDef": { + "colname": "inserted_at", + "constraints": [ + { + "Constraint": { + "contype": "CONSTR_DEFAULT", + "location": 124, + "raw_expr": { + "FuncCall": { + "args": [ + { + "TypeCast": { + "arg": { + "A_Const": { + "location": 141, + "sval": { + "sval": "utc", + }, + }, + }, + "location": 146, + "typeName": { + "location": 148, + "names": [ + { + "String": { + "sval": "text", + }, + }, + ], + "typemod": -1, + }, + }, + }, + { + "FuncCall": { + "funcformat": "COERCE_EXPLICIT_CALL", + "funcname": [ + { + "String": { + "sval": "now", + }, + }, + ], + "location": 154, + }, + }, + ], + "funcformat": "COERCE_EXPLICIT_CALL", + "funcname": [ + { + "String": { + "sval": "timezone", + }, + }, + ], + "location": 132, + }, + }, + }, + }, + { + "Constraint": { + "contype": "CONSTR_NOTNULL", + "location": 161, + }, + }, + ], + "is_local": true, + "location": 87, + "typeName": { + "location": 99, + "names": [ + { + "String": { + "sval": "pg_catalog", + }, + }, + { + "String": { + "sval": "timestamptz", + }, + }, + ], + "typemod": -1, + }, + }, + }, + { + "ColumnDef": { + "colname": "updated_at", + "constraints": [ + { + "Constraint": { + "contype": "CONSTR_DEFAULT", + "location": 209, + "raw_expr": { + "FuncCall": { + "args": [ + { + "TypeCast": { + "arg": { + "A_Const": { + "location": 226, + "sval": { + "sval": "utc", + }, + }, + }, + "location": 231, + "typeName": { + "location": 233, + "names": [ + { + "String": { + "sval": "text", + }, + }, + ], + "typemod": -1, + }, + }, + }, + { + "FuncCall": { + "funcformat": "COERCE_EXPLICIT_CALL", + "funcname": [ + { + "String": { + "sval": "now", + }, + }, + ], + "location": 239, + }, + }, + ], + "funcformat": "COERCE_EXPLICIT_CALL", + "funcname": [ + { + "String": { + "sval": "timezone", + }, + }, + ], + "location": 217, + }, + }, + }, + }, + { + "Constraint": { + "contype": "CONSTR_NOTNULL", + "location": 246, + }, + }, + ], + "is_local": true, + "location": 173, + "typeName": { + "location": 184, + "names": [ + { + "String": { + "sval": "pg_catalog", + }, + }, + { + "String": { + "sval": "timestamptz", + }, + }, + ], + "typemod": -1, + }, + }, + }, + { + "ColumnDef": { + "colname": "data", + "is_local": true, + "location": 258, + "typeName": { + "location": 263, + "names": [ + { + "String": { + "sval": "jsonb", + }, + }, + ], + "typemod": -1, + }, + }, + }, + { + "ColumnDef": { + "colname": "name", + "is_local": true, + "location": 272, + "typeName": { + "location": 277, + "names": [ + { + "String": { + "sval": "text", + }, + }, + ], + "typemod": -1, + }, + }, + }, + ], + }, + }, + "stmt_len": 283, + }, + ], + "version": 170004, + } + `) + + const deparse = await app.inject({ + method: 'POST', + path: '/query/deparse', + payload: { ast: res.json() }, + }) + expect(deparse.body).toMatchInlineSnapshot(` + "CREATE TABLE table_name ( + id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + inserted_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL, + updated_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL, + data jsonb, + name text + );" + `) +}) + +test('formatter', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query/format', + payload: { query: 'SELECT id, name FROM users where user_id = 1234' }, + }) + expect(res.body).toMatchInlineSnapshot(` + "SELECT + id, + name + FROM + users + where + user_id = 1234 + " + `) +}) + +test('very big number', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: `SELECT ${Number.MAX_SAFE_INTEGER + 1}::int8, ARRAY[${ + Number.MIN_SAFE_INTEGER - 1 + }::int8]`, + }, + }) + expect(res.json()).toMatchInlineSnapshot(` + [ + { + "array": [ + "-9007199254740992", + ], + "int8": "9007199254740992", + }, + ] + `) +}) + +// issue: https://github.com/supabase/supabase/issues/27626 +test('return interval as string', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: `SELECT '1 day 1 hour 45 minutes'::interval` }, + }) + expect(res.json()).toMatchInlineSnapshot(` + [ + { + "interval": "1 day 01:45:00", + }, + ] + `) +}) + +test('line position error', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: 'SELECT *\nFROM pg_class\nWHERE relname = missing_quotes;' }, + }) + expect(res.json()).toMatchInlineSnapshot(` + { + "code": "42703", + "error": "ERROR: 42703: column "missing_quotes" does not exist + LINE 3: WHERE relname = missing_quotes; + ^ + ", + "file": "parse_relation.c", + "formattedError": "ERROR: 42703: column "missing_quotes" does not exist + LINE 3: WHERE relname = missing_quotes; + ^ + ", + "length": 114, + "line": "3589", + "message": "column "missing_quotes" does not exist", + "name": "error", + "position": "40", + "routine": "errorMissingColumn", + "severity": "ERROR", + } + `) +}) + +test('error with additional details', async () => { + // This query will generate an error with details + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: `DO $$ + DECLARE + my_var int; + BEGIN + -- This will trigger an error with detail, hint, and context + SELECT * INTO STRICT my_var FROM (VALUES (1), (2)) AS t(v); + END $$;`, + }, + }) + + expect(res.json()).toMatchInlineSnapshot(` + { + "code": "P0003", + "error": "ERROR: P0003: query returned more than one row + HINT: Make sure the query returns a single row, or use LIMIT 1. + CONTEXT: PL/pgSQL function inline_code_block line 6 at SQL statement + ", + "file": "pl_exec.c", + "formattedError": "ERROR: P0003: query returned more than one row + HINT: Make sure the query returns a single row, or use LIMIT 1. + CONTEXT: PL/pgSQL function inline_code_block line 6 at SQL statement + ", + "hint": "Make sure the query returns a single row, or use LIMIT 1.", + "length": 216, + "line": "4349", + "message": "query returned more than one row", + "name": "error", + "routine": "exec_stmt_execsql", + "severity": "ERROR", + "where": "PL/pgSQL function inline_code_block line 6 at SQL statement", + } + `) +}) + +test('error with all formatting properties', async () => { + // This query will generate an error with all formatting properties + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + DO $$ + BEGIN + -- Using EXECUTE to force internal query to appear + EXECUTE 'SELECT * FROM nonexistent_table WHERE id = 1'; + EXCEPTION WHEN OTHERS THEN + -- Re-raise with added context + RAISE EXCEPTION USING + ERRCODE = SQLSTATE, + MESSAGE = SQLERRM, + DETAIL = 'This is additional detail information', + HINT = 'This is a hint for fixing the issue', + SCHEMA = 'public'; + END $$; + `, + }, + }) + + expect(res.json()).toMatchInlineSnapshot(` + { + "code": "42P01", + "detail": "This is additional detail information", + "error": "ERROR: 42P01: relation "nonexistent_table" does not exist + DETAIL: This is additional detail information + HINT: This is a hint for fixing the issue + CONTEXT: PL/pgSQL function inline_code_block line 7 at RAISE + ", + "file": "pl_exec.c", + "formattedError": "ERROR: 42P01: relation "nonexistent_table" does not exist + DETAIL: This is additional detail information + HINT: This is a hint for fixing the issue + CONTEXT: PL/pgSQL function inline_code_block line 7 at RAISE + ", + "hint": "This is a hint for fixing the issue", + "length": 242, + "line": "3859", + "message": "relation "nonexistent_table" does not exist", + "name": "error", + "routine": "exec_stmt_raise", + "schema": "public", + "severity": "ERROR", + "where": "PL/pgSQL function inline_code_block line 7 at RAISE", + } + `) +}) + +test('error with internalQuery property', async () => { + // First create a function that will execute a query internally + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE OR REPLACE FUNCTION test_internal_query() RETURNS void AS $$ + BEGIN + -- This query will be the "internal query" when it fails + EXECUTE 'SELECT * FROM nonexistent_table'; + RETURN; + END; + $$ LANGUAGE plpgsql; + `, + }, + }) + + // Now call the function to trigger the error with internalQuery + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: 'SELECT test_internal_query();' }, + }) + + expect(res.json()).toMatchInlineSnapshot(` + { + "code": "42P01", + "error": "ERROR: 42P01: relation "nonexistent_table" does not exist + QUERY: SELECT * FROM nonexistent_table + CONTEXT: PL/pgSQL function test_internal_query() line 4 at EXECUTE + ", + "file": "parse_relation.c", + "formattedError": "ERROR: 42P01: relation "nonexistent_table" does not exist + QUERY: SELECT * FROM nonexistent_table + CONTEXT: PL/pgSQL function test_internal_query() line 4 at EXECUTE + ", + "internalPosition": "15", + "internalQuery": "SELECT * FROM nonexistent_table", + "length": 208, + "line": "1381", + "message": "relation "nonexistent_table" does not exist", + "name": "error", + "routine": "parserOpenTable", + "severity": "ERROR", + "where": "PL/pgSQL function test_internal_query() line 4 at EXECUTE", + } + `) +}) + +test('custom application_name', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + headers: { 'x-pg-application-name': 'test' }, + payload: { query: 'SHOW application_name;' }, + }) + + expect(res.json()).toMatchInlineSnapshot(` + [ + { + "application_name": "test", + }, + ] + `) +}) + +test('parameter binding with positional parameters', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: 'SELECT * FROM users WHERE id = $1 AND status = $2', + parameters: [1, 'ACTIVE'], + }, + }) + expect(normalizeUuids(res.json())).toMatchInlineSnapshot(` + [ + { + "decimal": null, + "id": 1, + "name": "Joe Bloggs", + "status": "ACTIVE", + "user_uuid": "00000000-0000-0000-0000-000000000000", + }, + ] + `) +}) + +test('parameter binding with single parameter', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: 'SELECT name FROM users WHERE id = $1', parameters: [2] }, + }) + expect(res.json()).toMatchInlineSnapshot(` + [ + { + "name": "Jane Doe", + }, + ] + `) +}) + +test('parameter binding with no matches', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: 'SELECT * FROM users WHERE id = $1', parameters: [999] }, + }) + expect(res.json()).toMatchInlineSnapshot(`[]`) +}) + +test('no parameters field', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: 'SELECT COUNT(*) as count FROM users' }, + }) + expect(res.json()).toMatchInlineSnapshot(` + [ + { + "count": 2, + }, + ] + `) +}) + +test('parameter binding with empty parameters array', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: 'SELECT COUNT(*) as count FROM users', parameters: [] }, + }) + expect(res.json()).toMatchInlineSnapshot(` + [ + { + "count": 2, + }, + ] + `) +}) + +test('parameter binding error - wrong parameter count', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: 'SELECT * FROM users WHERE id = $1 AND status = $2', + parameters: [1], // Missing second parameter + }, + }) + expect(res.statusCode).toBe(400) + const json = res.json() + expect(json.code).toBe('08P01') + expect(json.message).toContain( + 'bind message supplies 1 parameters, but prepared statement "" requires 2' + ) +}) diff --git a/test/server/result-size-limit.ts b/test/server/result-size-limit.ts new file mode 100644 index 00000000..15543d67 --- /dev/null +++ b/test/server/result-size-limit.ts @@ -0,0 +1,140 @@ +import { expect, test, beforeAll, afterAll, describe } from 'vitest' +import { app } from './utils' +import { pgMeta } from '../lib/utils' + +describe('test max-result-size limit', () => { + // Create a table with large data for testing + beforeAll(async () => { + // Create a table with a large text column + await pgMeta.query(` + CREATE TABLE large_data ( + id SERIAL PRIMARY KEY, + data TEXT + ); + `) + await pgMeta.query(` + CREATE TABLE small_data ( + id SERIAL PRIMARY KEY, + data TEXT + ); + `) + + // Insert data that will exceed our limit in tests it's set around ~20MB + await pgMeta.query(` + INSERT INTO large_data (data) + SELECT repeat('x', 1024 * 1024) -- 1MB of data per row + FROM generate_series(1, 50); + `) + await pgMeta.query(` + INSERT INTO small_data (data) + SELECT repeat('x', 10 * 1024) -- 10KB per row + FROM generate_series(1, 50); + `) + }) + + afterAll(async () => { + // Clean up the test table + await pgMeta.query('DROP TABLE large_data;') + await pgMeta.query('DROP TABLE small_data;') + }) + + test('query exceeding result size limit', async () => { + // Set a small maxResultSize (50MB) + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: 'SELECT * FROM large_data;' }, + }) + + // Check that we get the proper error response + expect(res.statusCode).toBe(400) + expect(res.json()).toMatchObject({ + error: expect.stringContaining('Query result size'), + code: 'RESULT_SIZE_EXCEEDED', + resultSize: expect.any(Number), + maxResultSize: 20 * 1024 * 1024, + }) + + // Verify that subsequent queries still work and the server isn't killed + const nextRes = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: 'SELECT * FROM small_data;' }, + }) + + expect(nextRes.statusCode).toBe(200) + // Should have retrieve the 50 rows as expected + expect(nextRes.json()).toHaveLength(50) + }) +}) + +describe('test js parser error max result', () => { + // Create a table with large data for testing + beforeAll(async () => { + // Create a table with a large text column + await pgMeta.query(` + CREATE TABLE very_large_data ( + id SERIAL PRIMARY KEY, + data TEXT + ); + `) + + // Insert data that will exceed our limit in tests it's set around ~20MB + await pgMeta.query(` + INSERT INTO very_large_data (data) + VALUES (repeat('x', 710 * 1024 * 1024)) -- 700+MB string will raise a JS exception at parse time + `) + }) + + afterAll(async () => { + // Clean up the test table + await pgMeta.query('DROP TABLE very_large_data;') + }) + + test( + 'should not kill the server on underlying parser error', + async () => { + // Set a small maxResultSize (50MB) + const res = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: 'SELECT * FROM very_large_data;' }, + }) + + // Check that we get the proper error response from the underlying parser + expect(res.json()).toMatchInlineSnapshot(` + { + "error": "exception received while handling packet: Error: Cannot create a string longer than 0x1fffffe8 characters + ", + "formattedError": "exception received while handling packet: Error: Cannot create a string longer than 0x1fffffe8 characters + ", + "length": 744488975, + "message": "exception received while handling packet: Error: Cannot create a string longer than 0x1fffffe8 characters", + "name": "error", + } + `) + + // Verify that subsequent queries still work and the server isn't killed + const nextRes = await app.inject({ + method: 'POST', + path: '/query', + payload: { query: 'SELECT * FROM todos;' }, + }) + expect(nextRes.json()).toMatchInlineSnapshot(` + [ + { + "details": "Star the repo", + "id": 1, + "user-id": 1, + }, + { + "details": "Watch the releases", + "id": 2, + "user-id": 2, + }, + ] + `) + }, + { timeout: 20000 } + ) +}) diff --git a/test/server/ssl.ts b/test/server/ssl.ts new file mode 100644 index 00000000..5b6cc83b --- /dev/null +++ b/test/server/ssl.ts @@ -0,0 +1,128 @@ +import CryptoJS from 'crypto-js' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { expect, test } from 'vitest' +import { app } from './utils' +import { CRYPTO_KEY, DEFAULT_POOL_CONFIG } from '../../src/server/constants' + +// @ts-ignore: Harmless type error on import.meta. +const cwd = path.dirname(fileURLToPath(import.meta.url)) +const sslRootCertPath = path.join(cwd, '../db/server.crt') +const sslRootCert = fs.readFileSync(sslRootCertPath, { encoding: 'utf8' }) + +test('query with no ssl', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + headers: { + 'x-connection-encrypted': CryptoJS.AES.encrypt( + 'postgresql://postgres:postgres@localhost:5432/postgres', + CRYPTO_KEY + ).toString(), + }, + payload: { query: 'select 1;' }, + }) + expect(res.json()).toMatchInlineSnapshot(` + [ + { + "?column?": 1, + }, + ] + `) +}) + +test('query with ssl w/o root cert', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + headers: { + 'x-connection-encrypted': CryptoJS.AES.encrypt( + 'postgresql://postgres:postgres@localhost:5432/postgres?sslmode=verify-full', + CRYPTO_KEY + ).toString(), + }, + payload: { query: 'select 1;' }, + }) + expect(res.json()?.error).toMatch(/^self[ -]signed certificate$/) +}) + +test('query with ssl with root cert', async () => { + const defaultSsl = DEFAULT_POOL_CONFIG.ssl + DEFAULT_POOL_CONFIG.ssl = { ca: sslRootCert } + + const res = await app.inject({ + method: 'POST', + path: '/query', + headers: { + 'x-connection-encrypted': CryptoJS.AES.encrypt( + `postgresql://postgres:postgres@localhost:5432/postgres?sslmode=verify-full`, + CRYPTO_KEY + ).toString(), + }, + payload: { query: 'select 1;' }, + }) + expect(res.json()).toMatchInlineSnapshot(` + [ + { + "?column?": 1, + }, + ] + `) + + DEFAULT_POOL_CONFIG.ssl = defaultSsl +}) + +test('query with invalid space empty encrypted connection string', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + headers: { + 'x-connection-encrypted': CryptoJS.AES.encrypt(` `, CRYPTO_KEY).toString(), + }, + payload: { query: 'select 1;' }, + }) + expect(res.statusCode).toBe(500) + expect(res.json()).toMatchInlineSnapshot(` + { + "error": "failed to get upstream connection details", + } + `) +}) + +test('query with invalid empty encrypted connection string', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + headers: { + 'x-connection-encrypted': CryptoJS.AES.encrypt(``, CRYPTO_KEY).toString(), + }, + payload: { query: 'select 1;' }, + }) + expect(res.statusCode).toBe(500) + expect(res.json()).toMatchInlineSnapshot(` + { + "error": "failed to get upstream connection details", + } + `) +}) + +test('query with missing host connection string encrypted connection string', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + headers: { + 'x-connection-encrypted': CryptoJS.AES.encrypt( + `postgres://name:password@:5432/postgres?sslmode=prefer`, + CRYPTO_KEY + ).toString(), + }, + payload: { query: 'select 1;' }, + }) + expect(res.statusCode).toBe(500) + expect(res.json()).toMatchInlineSnapshot(` + { + "error": "failed to process upstream connection details", + } + `) +}) diff --git a/test/server/table-privileges.ts b/test/server/table-privileges.ts new file mode 100644 index 00000000..732510ef --- /dev/null +++ b/test/server/table-privileges.ts @@ -0,0 +1,377 @@ +import { expect, test } from 'vitest' +import { PostgresTablePrivileges } from '../../src/lib/types' +import { app } from './utils' + +test('list table privileges', async () => { + const res = await app.inject({ method: 'GET', path: '/table-privileges' }) + expect( + res + .json() + .find(({ schema, name }) => schema === 'public' && name === 'todos') + ).toMatchInlineSnapshot( + { relation_id: expect.any(Number) }, + ` + { + "kind": "table", + "name": "todos", + "privileges": [ + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "INSERT", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "SELECT", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "UPDATE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "DELETE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "TRUNCATE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "REFERENCES", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "TRIGGER", + }, + ], + "relation_id": Any, + "schema": "public", + } + ` + ) +}) + +test('revoke & grant table privileges', async () => { + let res = await app.inject({ method: 'GET', path: '/table-privileges' }) + const { relation_id } = res + .json() + .find(({ schema, name }) => schema === 'public' && name === 'todos')! + + res = await app.inject({ + method: 'DELETE', + path: '/table-privileges', + payload: [ + { + relation_id, + grantee: 'postgres', + privilege_type: 'ALL', + }, + ], + }) + let privs = res.json() + expect(privs.length).toBe(1) + expect(privs[0]).toMatchInlineSnapshot( + { relation_id: expect.any(Number) }, + ` + { + "kind": "table", + "name": "todos", + "privileges": [], + "relation_id": Any, + "schema": "public", + } + ` + ) + + res = await app.inject({ + method: 'POST', + path: '/table-privileges', + payload: [ + { + relation_id, + grantee: 'postgres', + privilege_type: 'ALL', + }, + ], + }) + privs = res.json() + expect(privs.length).toBe(1) + expect(privs[0]).toMatchInlineSnapshot( + { relation_id: expect.any(Number) }, + ` + { + "kind": "table", + "name": "todos", + "privileges": [ + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "TRIGGER", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "REFERENCES", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "TRUNCATE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "DELETE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "UPDATE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "SELECT", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "INSERT", + }, + ], + "relation_id": Any, + "schema": "public", + } + ` + ) +}) + +test('revoke & grant table privileges w/ quoted table name', async () => { + let res = await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: `create role r; create schema "s 1"; create table "s 1"."t 1"();`, + }, + }) + if (res.json().error) { + throw new Error(res.payload) + } + + res = await app.inject({ method: 'GET', path: '/table-privileges' }) + const { relation_id } = res + .json() + .find(({ schema, name }) => schema === 's 1' && name === 't 1')! + + res = await app.inject({ + method: 'POST', + path: '/table-privileges', + payload: [ + { + relation_id, + grantee: 'r', + privilege_type: 'ALL', + }, + ], + }) + let privs = res.json() + expect(privs.length).toBe(1) + expect(privs[0]).toMatchInlineSnapshot( + { relation_id: expect.any(Number) }, + ` + { + "kind": "table", + "name": "t 1", + "privileges": [ + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "TRIGGER", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "REFERENCES", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "TRUNCATE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "DELETE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "UPDATE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "SELECT", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "INSERT", + }, + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "TRIGGER", + }, + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "REFERENCES", + }, + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "TRUNCATE", + }, + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "DELETE", + }, + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "UPDATE", + }, + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "SELECT", + }, + { + "grantee": "r", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "INSERT", + }, + ], + "relation_id": Any, + "schema": "s 1", + } + ` + ) + + res = await app.inject({ + method: 'DELETE', + path: '/table-privileges', + payload: [ + { + relation_id, + grantee: 'r', + privilege_type: 'ALL', + }, + ], + }) + privs = res.json() + expect(privs.length).toBe(1) + expect(privs[0]).toMatchInlineSnapshot( + { relation_id: expect.any(Number) }, + ` + { + "kind": "table", + "name": "t 1", + "privileges": [ + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "TRIGGER", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "REFERENCES", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "TRUNCATE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "DELETE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "UPDATE", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "SELECT", + }, + { + "grantee": "postgres", + "grantor": "postgres", + "is_grantable": false, + "privilege_type": "INSERT", + }, + ], + "relation_id": Any, + "schema": "s 1", + } + ` + ) + + res = await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: `drop role r; drop schema "s 1" cascade;`, + }, + }) + if (res.json().error) { + throw new Error(res.payload) + } +}) diff --git a/test/server/typegen.ts b/test/server/typegen.ts new file mode 100644 index 00000000..d71b468e --- /dev/null +++ b/test/server/typegen.ts @@ -0,0 +1,6761 @@ +import { expect, test } from 'vitest' +import { app } from './utils' + +test('typegen: typescript', async () => { + const { body } = await app.inject({ method: 'GET', path: '/generators/typescript' }) + expect(body).toMatchInlineSnapshot( + ` + "export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + + export type Database = { + public: { + Tables: { + category: { + Row: { + id: number + name: string + } + Insert: { + id?: number + name: string + } + Update: { + id?: number + name?: string + } + Relationships: [] + } + empty: { + Row: {} + Insert: {} + Update: {} + Relationships: [] + } + events: { + Row: { + created_at: string + data: Json | null + event_type: string | null + id: number + days_since_event: number | null + } + Insert: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Update: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Relationships: [] + } + events_2024: { + Row: { + created_at: string + data: Json | null + event_type: string | null + id: number + } + Insert: { + created_at?: string + data?: Json | null + event_type?: string | null + id: number + } + Update: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Relationships: [] + } + events_2025: { + Row: { + created_at: string + data: Json | null + event_type: string | null + id: number + } + Insert: { + created_at?: string + data?: Json | null + event_type?: string | null + id: number + } + Update: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Relationships: [] + } + foreign_table: { + Row: { + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + } + Insert: { + id: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + } + Update: { + id?: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + } + Relationships: [] + } + memes: { + Row: { + category: number | null + created_at: string + id: number + metadata: Json | null + name: string + status: Database["public"]["Enums"]["meme_status"] | null + } + Insert: { + category?: number | null + created_at: string + id?: number + metadata?: Json | null + name: string + status?: Database["public"]["Enums"]["meme_status"] | null + } + Update: { + category?: number | null + created_at?: string + id?: number + metadata?: Json | null + name?: string + status?: Database["public"]["Enums"]["meme_status"] | null + } + Relationships: [ + { + foreignKeyName: "memes_category_fkey" + columns: ["category"] + referencedRelation: "category" + referencedColumns: ["id"] + }, + ] + } + table_with_other_tables_row_type: { + Row: { + col1: Database["public"]["Tables"]["user_details"]["Row"] | null + col2: Database["public"]["Views"]["a_view"]["Row"] | null + } + Insert: { + col1?: Database["public"]["Tables"]["user_details"]["Row"] | null + col2?: Database["public"]["Views"]["a_view"]["Row"] | null + } + Update: { + col1?: Database["public"]["Tables"]["user_details"]["Row"] | null + col2?: Database["public"]["Views"]["a_view"]["Row"] | null + } + Relationships: [] + } + table_with_primary_key_other_than_id: { + Row: { + name: string | null + other_id: number + } + Insert: { + name?: string | null + other_id?: number + } + Update: { + name?: string | null + other_id?: number + } + Relationships: [] + } + todos: { + Row: { + details: string | null + id: number + "user-id": number + blurb: string | null + blurb_varchar: string | null + details_is_long: boolean | null + details_length: number | null + details_words: string[] | null + test_unnamed_row_scalar: number | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null + } + Insert: { + details?: string | null + id?: number + "user-id": number + } + Update: { + details?: string | null + id?: number + "user-id"?: number + } + Relationships: [ + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + user_details: { + Row: { + details: string | null + user_id: number + } + Insert: { + details?: string | null + user_id: number + } + Update: { + details?: string | null + user_id?: number + } + Relationships: [ + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + users: { + Row: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + test_unnamed_row_composite: + | Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null + } + Insert: { + decimal?: number | null + id?: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Update: { + decimal?: number | null + id?: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Relationships: [] + } + users_audit: { + Row: { + created_at: string | null + id: number + previous_value: Json | null + user_id: number | null + created_ago: number | null + } + Insert: { + created_at?: string | null + id?: number + previous_value?: Json | null + user_id?: number | null + } + Update: { + created_at?: string | null + id?: number + previous_value?: Json | null + user_id?: number | null + } + Relationships: [] + } + } + Views: { + a_view: { + Row: { + id: number | null + } + Insert: { + id?: number | null + } + Update: { + id?: number | null + } + Relationships: [] + } + todos_matview: { + Row: { + details: string | null + id: number | null + "user-id": number | null + get_todos_by_matview: { + details: string | null + id: number + "user-id": number + } | null + } + Relationships: [ + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + todos_view: { + Row: { + details: string | null + id: number | null + "user-id": number | null + blurb_varchar: string | null + test_unnamed_view_row: { + details: string | null + id: number + "user-id": number + } | null + } + Insert: { + details?: string | null + id?: number | null + "user-id"?: number | null + } + Update: { + details?: string | null + id?: number | null + "user-id"?: number | null + } + Relationships: [ + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + user_todos_summary_view: { + Row: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + Relationships: [] + } + users_view: { + Row: { + decimal: number | null + id: number | null + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + } + Insert: { + decimal?: number | null + id?: number | null + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Update: { + decimal?: number | null + id?: number | null + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Relationships: [] + } + users_view_with_multiple_refs_to_users: { + Row: { + initial_id: number | null + initial_name: string | null + second_id: number | null + second_name: string | null + } + Relationships: [] + } + } + Functions: { + blurb: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.blurb with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + blurb_varchar: + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + | { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + created_ago: { + Args: { "": Database["public"]["Tables"]["users_audit"]["Row"] } + Returns: { + error: true + } & "the function public.created_ago with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + days_since_event: { + Args: { "": Database["public"]["Tables"]["events"]["Row"] } + Returns: { + error: true + } & "the function public.days_since_event with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + details_is_long: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.details_is_long with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + details_length: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.details_length with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + details_words: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.details_words with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + function_returning_row: { + Args: never + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + } + SetofOptions: { + from: "*" + to: "users" + isOneToOne: true + isSetofReturn: false + } + } + function_returning_set_of_rows: { + Args: never + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + function_returning_single_row: { + Args: { todos: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + } + SetofOptions: { + from: "todos" + to: "users" + isOneToOne: true + isSetofReturn: false + } + } + function_returning_table: { + Args: never + Returns: { + id: number + name: string + }[] + } + function_returning_table_with_args: { + Args: { user_id: number } + Returns: { + id: number + name: string + }[] + } + function_using_setof_rows_one: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: true + } + } + function_using_table_returns: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: false + } + } + get_composite_type_data: { + Args: never + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][] + SetofOptions: { + from: "*" + to: "composite_type_with_array_attribute" + isOneToOne: false + isSetofReturn: true + } + } + get_single_user_summary_from_view: + | { + Args: { search_user_id: number } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "*" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users_view" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + get_todos_by_matview: { + Args: { "": unknown } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "todos_matview" + to: "todos" + isOneToOne: true + isSetofReturn: true + } + } + get_todos_from_user: + | { + Args: { search_user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + get_todos_setof_rows: + | { + Args: { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + get_user_audit_setof_single_row: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + created_at: string | null + id: number + previous_value: Json | null + user_id: number | null + } + SetofOptions: { + from: "users" + to: "users_audit" + isOneToOne: true + isSetofReturn: true + } + } + get_user_ids: { Args: never; Returns: number[] } + get_user_summary: { Args: never; Returns: Record[] } + polymorphic_function: { Args: { "": string }; Returns: undefined } + polymorphic_function_with_different_return: { + Args: { "": string } + Returns: string + } + polymorphic_function_with_no_params_or_unnamed: + | { Args: never; Returns: number } + | { Args: { "": string }; Returns: string } + polymorphic_function_with_unnamed_default: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default(), public.polymorphic_function_with_unnamed_default( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_default_overload: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default_overload(), public.polymorphic_function_with_unnamed_default_overload( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_json: { + Args: { "": Json } + Returns: number + } + polymorphic_function_with_unnamed_jsonb: { + Args: { "": Json } + Returns: number + } + polymorphic_function_with_unnamed_text: { + Args: { "": string } + Returns: number + } + postgres_fdw_disconnect: { Args: { "": string }; Returns: boolean } + postgres_fdw_disconnect_all: { Args: never; Returns: boolean } + postgres_fdw_get_connections: { + Args: never + Returns: Record[] + } + postgres_fdw_handler: { Args: never; Returns: unknown } + postgrest_resolvable_with_override_function: + | { Args: never; Returns: undefined } + | { Args: { a: string }; Returns: number } + | { Args: { b: number }; Returns: string } + | { + Args: { completed: boolean; todo_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_id: number } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + postgrest_unresolvable_function: + | { Args: never; Returns: undefined } + | { + Args: { a: number } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { + Args: { a: string } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + search_todos_by_details: { + Args: { search_details: string } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + test_internal_query: { Args: never; Returns: undefined } + test_unnamed_row_composite: { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + SetofOptions: { + from: "users" + to: "composite_type_with_array_attribute" + isOneToOne: true + isSetofReturn: false + } + } + test_unnamed_row_scalar: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.test_unnamed_row_scalar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + test_unnamed_row_setof: + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + test_unnamed_view_row: { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + } + Enums: { + meme_status: "new" | "old" | "retired" + user_status: "ACTIVE" | "INACTIVE" + } + CompositeTypes: { + composite_type_with_array_attribute: { + my_text_array: string[] | null + } + composite_type_with_record_attribute: { + todo: Database["public"]["Tables"]["todos"]["Row"] | null + } + } + } + } + + type DatabaseWithoutInternals = Omit + + type DefaultSchema = DatabaseWithoutInternals[Extract] + + export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never, + > = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + + export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, + > = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + + export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, + > = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + + export type Enums< + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof DatabaseWithoutInternals }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never, + > = DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never + + export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof DatabaseWithoutInternals }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never, + > = PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never + + export const Constants = { + public: { + Enums: { + meme_status: ["new", "old", "retired"], + user_status: ["ACTIVE", "INACTIVE"], + }, + }, + } as const + " + ` + ) +}) + +test('typegen w/ one-to-one relationships', async () => { + const { body } = await app.inject({ + method: 'GET', + path: '/generators/typescript', + query: { detect_one_to_one_relationships: 'true' }, + }) + expect(body).toMatchInlineSnapshot( + ` + "export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + + export type Database = { + public: { + Tables: { + category: { + Row: { + id: number + name: string + } + Insert: { + id?: number + name: string + } + Update: { + id?: number + name?: string + } + Relationships: [] + } + empty: { + Row: {} + Insert: {} + Update: {} + Relationships: [] + } + events: { + Row: { + created_at: string + data: Json | null + event_type: string | null + id: number + days_since_event: number | null + } + Insert: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Update: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Relationships: [] + } + events_2024: { + Row: { + created_at: string + data: Json | null + event_type: string | null + id: number + } + Insert: { + created_at?: string + data?: Json | null + event_type?: string | null + id: number + } + Update: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Relationships: [] + } + events_2025: { + Row: { + created_at: string + data: Json | null + event_type: string | null + id: number + } + Insert: { + created_at?: string + data?: Json | null + event_type?: string | null + id: number + } + Update: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Relationships: [] + } + foreign_table: { + Row: { + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + } + Insert: { + id: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + } + Update: { + id?: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + } + Relationships: [] + } + memes: { + Row: { + category: number | null + created_at: string + id: number + metadata: Json | null + name: string + status: Database["public"]["Enums"]["meme_status"] | null + } + Insert: { + category?: number | null + created_at: string + id?: number + metadata?: Json | null + name: string + status?: Database["public"]["Enums"]["meme_status"] | null + } + Update: { + category?: number | null + created_at?: string + id?: number + metadata?: Json | null + name?: string + status?: Database["public"]["Enums"]["meme_status"] | null + } + Relationships: [ + { + foreignKeyName: "memes_category_fkey" + columns: ["category"] + isOneToOne: false + referencedRelation: "category" + referencedColumns: ["id"] + }, + ] + } + table_with_other_tables_row_type: { + Row: { + col1: Database["public"]["Tables"]["user_details"]["Row"] | null + col2: Database["public"]["Views"]["a_view"]["Row"] | null + } + Insert: { + col1?: Database["public"]["Tables"]["user_details"]["Row"] | null + col2?: Database["public"]["Views"]["a_view"]["Row"] | null + } + Update: { + col1?: Database["public"]["Tables"]["user_details"]["Row"] | null + col2?: Database["public"]["Views"]["a_view"]["Row"] | null + } + Relationships: [] + } + table_with_primary_key_other_than_id: { + Row: { + name: string | null + other_id: number + } + Insert: { + name?: string | null + other_id?: number + } + Update: { + name?: string | null + other_id?: number + } + Relationships: [] + } + todos: { + Row: { + details: string | null + id: number + "user-id": number + blurb: string | null + blurb_varchar: string | null + details_is_long: boolean | null + details_length: number | null + details_words: string[] | null + test_unnamed_row_scalar: number | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null + } + Insert: { + details?: string | null + id?: number + "user-id": number + } + Update: { + details?: string | null + id?: number + "user-id"?: number + } + Relationships: [ + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + user_details: { + Row: { + details: string | null + user_id: number + } + Insert: { + details?: string | null + user_id: number + } + Update: { + details?: string | null + user_id?: number + } + Relationships: [ + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + users: { + Row: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + test_unnamed_row_composite: + | Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null + } + Insert: { + decimal?: number | null + id?: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Update: { + decimal?: number | null + id?: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Relationships: [] + } + users_audit: { + Row: { + created_at: string | null + id: number + previous_value: Json | null + user_id: number | null + created_ago: number | null + } + Insert: { + created_at?: string | null + id?: number + previous_value?: Json | null + user_id?: number | null + } + Update: { + created_at?: string | null + id?: number + previous_value?: Json | null + user_id?: number | null + } + Relationships: [] + } + } + Views: { + a_view: { + Row: { + id: number | null + } + Insert: { + id?: number | null + } + Update: { + id?: number | null + } + Relationships: [] + } + todos_matview: { + Row: { + details: string | null + id: number | null + "user-id": number | null + get_todos_by_matview: { + details: string | null + id: number + "user-id": number + } | null + } + Relationships: [ + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + todos_view: { + Row: { + details: string | null + id: number | null + "user-id": number | null + blurb_varchar: string | null + test_unnamed_view_row: { + details: string | null + id: number + "user-id": number + } | null + } + Insert: { + details?: string | null + id?: number | null + "user-id"?: number | null + } + Update: { + details?: string | null + id?: number | null + "user-id"?: number | null + } + Relationships: [ + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + user_todos_summary_view: { + Row: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + Relationships: [] + } + users_view: { + Row: { + decimal: number | null + id: number | null + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + } + Insert: { + decimal?: number | null + id?: number | null + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Update: { + decimal?: number | null + id?: number | null + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Relationships: [] + } + users_view_with_multiple_refs_to_users: { + Row: { + initial_id: number | null + initial_name: string | null + second_id: number | null + second_name: string | null + } + Relationships: [] + } + } + Functions: { + blurb: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.blurb with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + blurb_varchar: + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + | { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + created_ago: { + Args: { "": Database["public"]["Tables"]["users_audit"]["Row"] } + Returns: { + error: true + } & "the function public.created_ago with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + days_since_event: { + Args: { "": Database["public"]["Tables"]["events"]["Row"] } + Returns: { + error: true + } & "the function public.days_since_event with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + details_is_long: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.details_is_long with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + details_length: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.details_length with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + details_words: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.details_words with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + function_returning_row: { + Args: never + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + } + SetofOptions: { + from: "*" + to: "users" + isOneToOne: true + isSetofReturn: false + } + } + function_returning_set_of_rows: { + Args: never + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + function_returning_single_row: { + Args: { todos: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + } + SetofOptions: { + from: "todos" + to: "users" + isOneToOne: true + isSetofReturn: false + } + } + function_returning_table: { + Args: never + Returns: { + id: number + name: string + }[] + } + function_returning_table_with_args: { + Args: { user_id: number } + Returns: { + id: number + name: string + }[] + } + function_using_setof_rows_one: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: true + } + } + function_using_table_returns: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: false + } + } + get_composite_type_data: { + Args: never + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][] + SetofOptions: { + from: "*" + to: "composite_type_with_array_attribute" + isOneToOne: false + isSetofReturn: true + } + } + get_single_user_summary_from_view: + | { + Args: { search_user_id: number } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "*" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users_view" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + get_todos_by_matview: { + Args: { "": unknown } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "todos_matview" + to: "todos" + isOneToOne: true + isSetofReturn: true + } + } + get_todos_from_user: + | { + Args: { search_user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + get_todos_setof_rows: + | { + Args: { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + get_user_audit_setof_single_row: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + created_at: string | null + id: number + previous_value: Json | null + user_id: number | null + } + SetofOptions: { + from: "users" + to: "users_audit" + isOneToOne: true + isSetofReturn: true + } + } + get_user_ids: { Args: never; Returns: number[] } + get_user_summary: { Args: never; Returns: Record[] } + polymorphic_function: { Args: { "": string }; Returns: undefined } + polymorphic_function_with_different_return: { + Args: { "": string } + Returns: string + } + polymorphic_function_with_no_params_or_unnamed: + | { Args: never; Returns: number } + | { Args: { "": string }; Returns: string } + polymorphic_function_with_unnamed_default: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default(), public.polymorphic_function_with_unnamed_default( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_default_overload: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default_overload(), public.polymorphic_function_with_unnamed_default_overload( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_json: { + Args: { "": Json } + Returns: number + } + polymorphic_function_with_unnamed_jsonb: { + Args: { "": Json } + Returns: number + } + polymorphic_function_with_unnamed_text: { + Args: { "": string } + Returns: number + } + postgres_fdw_disconnect: { Args: { "": string }; Returns: boolean } + postgres_fdw_disconnect_all: { Args: never; Returns: boolean } + postgres_fdw_get_connections: { + Args: never + Returns: Record[] + } + postgres_fdw_handler: { Args: never; Returns: unknown } + postgrest_resolvable_with_override_function: + | { Args: never; Returns: undefined } + | { Args: { a: string }; Returns: number } + | { Args: { b: number }; Returns: string } + | { + Args: { completed: boolean; todo_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_id: number } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + postgrest_unresolvable_function: + | { Args: never; Returns: undefined } + | { + Args: { a: number } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { + Args: { a: string } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + search_todos_by_details: { + Args: { search_details: string } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + test_internal_query: { Args: never; Returns: undefined } + test_unnamed_row_composite: { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + SetofOptions: { + from: "users" + to: "composite_type_with_array_attribute" + isOneToOne: true + isSetofReturn: false + } + } + test_unnamed_row_scalar: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.test_unnamed_row_scalar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + test_unnamed_row_setof: + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + test_unnamed_view_row: { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + } + Enums: { + meme_status: "new" | "old" | "retired" + user_status: "ACTIVE" | "INACTIVE" + } + CompositeTypes: { + composite_type_with_array_attribute: { + my_text_array: string[] | null + } + composite_type_with_record_attribute: { + todo: Database["public"]["Tables"]["todos"]["Row"] | null + } + } + } + } + + type DatabaseWithoutInternals = Omit + + type DefaultSchema = DatabaseWithoutInternals[Extract] + + export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never, + > = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + + export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, + > = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + + export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, + > = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + + export type Enums< + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof DatabaseWithoutInternals }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never, + > = DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never + + export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof DatabaseWithoutInternals }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never, + > = PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never + + export const Constants = { + public: { + Enums: { + meme_status: ["new", "old", "retired"], + user_status: ["ACTIVE", "INACTIVE"], + }, + }, + } as const + " + ` + ) +}) + +test('typegen: typescript w/ one-to-one relationships', async () => { + const { body } = await app.inject({ + method: 'GET', + path: '/generators/typescript', + query: { detect_one_to_one_relationships: 'true' }, + }) + expect(body).toMatchInlineSnapshot( + ` + "export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + + export type Database = { + public: { + Tables: { + category: { + Row: { + id: number + name: string + } + Insert: { + id?: number + name: string + } + Update: { + id?: number + name?: string + } + Relationships: [] + } + empty: { + Row: {} + Insert: {} + Update: {} + Relationships: [] + } + events: { + Row: { + created_at: string + data: Json | null + event_type: string | null + id: number + days_since_event: number | null + } + Insert: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Update: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Relationships: [] + } + events_2024: { + Row: { + created_at: string + data: Json | null + event_type: string | null + id: number + } + Insert: { + created_at?: string + data?: Json | null + event_type?: string | null + id: number + } + Update: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Relationships: [] + } + events_2025: { + Row: { + created_at: string + data: Json | null + event_type: string | null + id: number + } + Insert: { + created_at?: string + data?: Json | null + event_type?: string | null + id: number + } + Update: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Relationships: [] + } + foreign_table: { + Row: { + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + } + Insert: { + id: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + } + Update: { + id?: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + } + Relationships: [] + } + memes: { + Row: { + category: number | null + created_at: string + id: number + metadata: Json | null + name: string + status: Database["public"]["Enums"]["meme_status"] | null + } + Insert: { + category?: number | null + created_at: string + id?: number + metadata?: Json | null + name: string + status?: Database["public"]["Enums"]["meme_status"] | null + } + Update: { + category?: number | null + created_at?: string + id?: number + metadata?: Json | null + name?: string + status?: Database["public"]["Enums"]["meme_status"] | null + } + Relationships: [ + { + foreignKeyName: "memes_category_fkey" + columns: ["category"] + isOneToOne: false + referencedRelation: "category" + referencedColumns: ["id"] + }, + ] + } + table_with_other_tables_row_type: { + Row: { + col1: Database["public"]["Tables"]["user_details"]["Row"] | null + col2: Database["public"]["Views"]["a_view"]["Row"] | null + } + Insert: { + col1?: Database["public"]["Tables"]["user_details"]["Row"] | null + col2?: Database["public"]["Views"]["a_view"]["Row"] | null + } + Update: { + col1?: Database["public"]["Tables"]["user_details"]["Row"] | null + col2?: Database["public"]["Views"]["a_view"]["Row"] | null + } + Relationships: [] + } + table_with_primary_key_other_than_id: { + Row: { + name: string | null + other_id: number + } + Insert: { + name?: string | null + other_id?: number + } + Update: { + name?: string | null + other_id?: number + } + Relationships: [] + } + todos: { + Row: { + details: string | null + id: number + "user-id": number + blurb: string | null + blurb_varchar: string | null + details_is_long: boolean | null + details_length: number | null + details_words: string[] | null + test_unnamed_row_scalar: number | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null + } + Insert: { + details?: string | null + id?: number + "user-id": number + } + Update: { + details?: string | null + id?: number + "user-id"?: number + } + Relationships: [ + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + user_details: { + Row: { + details: string | null + user_id: number + } + Insert: { + details?: string | null + user_id: number + } + Update: { + details?: string | null + user_id?: number + } + Relationships: [ + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + users: { + Row: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + test_unnamed_row_composite: + | Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null + } + Insert: { + decimal?: number | null + id?: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Update: { + decimal?: number | null + id?: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Relationships: [] + } + users_audit: { + Row: { + created_at: string | null + id: number + previous_value: Json | null + user_id: number | null + created_ago: number | null + } + Insert: { + created_at?: string | null + id?: number + previous_value?: Json | null + user_id?: number | null + } + Update: { + created_at?: string | null + id?: number + previous_value?: Json | null + user_id?: number | null + } + Relationships: [] + } + } + Views: { + a_view: { + Row: { + id: number | null + } + Insert: { + id?: number | null + } + Update: { + id?: number | null + } + Relationships: [] + } + todos_matview: { + Row: { + details: string | null + id: number | null + "user-id": number | null + get_todos_by_matview: { + details: string | null + id: number + "user-id": number + } | null + } + Relationships: [ + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + todos_view: { + Row: { + details: string | null + id: number | null + "user-id": number | null + blurb_varchar: string | null + test_unnamed_view_row: { + details: string | null + id: number + "user-id": number + } | null + } + Insert: { + details?: string | null + id?: number | null + "user-id"?: number | null + } + Update: { + details?: string | null + id?: number | null + "user-id"?: number | null + } + Relationships: [ + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + user_todos_summary_view: { + Row: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + Relationships: [] + } + users_view: { + Row: { + decimal: number | null + id: number | null + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + } + Insert: { + decimal?: number | null + id?: number | null + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Update: { + decimal?: number | null + id?: number | null + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Relationships: [] + } + users_view_with_multiple_refs_to_users: { + Row: { + initial_id: number | null + initial_name: string | null + second_id: number | null + second_name: string | null + } + Relationships: [] + } + } + Functions: { + blurb: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.blurb with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + blurb_varchar: + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + | { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + created_ago: { + Args: { "": Database["public"]["Tables"]["users_audit"]["Row"] } + Returns: { + error: true + } & "the function public.created_ago with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + days_since_event: { + Args: { "": Database["public"]["Tables"]["events"]["Row"] } + Returns: { + error: true + } & "the function public.days_since_event with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + details_is_long: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.details_is_long with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + details_length: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.details_length with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + details_words: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.details_words with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + function_returning_row: { + Args: never + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + } + SetofOptions: { + from: "*" + to: "users" + isOneToOne: true + isSetofReturn: false + } + } + function_returning_set_of_rows: { + Args: never + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + function_returning_single_row: { + Args: { todos: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + } + SetofOptions: { + from: "todos" + to: "users" + isOneToOne: true + isSetofReturn: false + } + } + function_returning_table: { + Args: never + Returns: { + id: number + name: string + }[] + } + function_returning_table_with_args: { + Args: { user_id: number } + Returns: { + id: number + name: string + }[] + } + function_using_setof_rows_one: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: true + } + } + function_using_table_returns: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: false + } + } + get_composite_type_data: { + Args: never + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][] + SetofOptions: { + from: "*" + to: "composite_type_with_array_attribute" + isOneToOne: false + isSetofReturn: true + } + } + get_single_user_summary_from_view: + | { + Args: { search_user_id: number } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "*" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users_view" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + get_todos_by_matview: { + Args: { "": unknown } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "todos_matview" + to: "todos" + isOneToOne: true + isSetofReturn: true + } + } + get_todos_from_user: + | { + Args: { search_user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + get_todos_setof_rows: + | { + Args: { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + get_user_audit_setof_single_row: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + created_at: string | null + id: number + previous_value: Json | null + user_id: number | null + } + SetofOptions: { + from: "users" + to: "users_audit" + isOneToOne: true + isSetofReturn: true + } + } + get_user_ids: { Args: never; Returns: number[] } + get_user_summary: { Args: never; Returns: Record[] } + polymorphic_function: { Args: { "": string }; Returns: undefined } + polymorphic_function_with_different_return: { + Args: { "": string } + Returns: string + } + polymorphic_function_with_no_params_or_unnamed: + | { Args: never; Returns: number } + | { Args: { "": string }; Returns: string } + polymorphic_function_with_unnamed_default: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default(), public.polymorphic_function_with_unnamed_default( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_default_overload: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default_overload(), public.polymorphic_function_with_unnamed_default_overload( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_json: { + Args: { "": Json } + Returns: number + } + polymorphic_function_with_unnamed_jsonb: { + Args: { "": Json } + Returns: number + } + polymorphic_function_with_unnamed_text: { + Args: { "": string } + Returns: number + } + postgres_fdw_disconnect: { Args: { "": string }; Returns: boolean } + postgres_fdw_disconnect_all: { Args: never; Returns: boolean } + postgres_fdw_get_connections: { + Args: never + Returns: Record[] + } + postgres_fdw_handler: { Args: never; Returns: unknown } + postgrest_resolvable_with_override_function: + | { Args: never; Returns: undefined } + | { Args: { a: string }; Returns: number } + | { Args: { b: number }; Returns: string } + | { + Args: { completed: boolean; todo_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_id: number } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + postgrest_unresolvable_function: + | { Args: never; Returns: undefined } + | { + Args: { a: number } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { + Args: { a: string } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + search_todos_by_details: { + Args: { search_details: string } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + test_internal_query: { Args: never; Returns: undefined } + test_unnamed_row_composite: { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + SetofOptions: { + from: "users" + to: "composite_type_with_array_attribute" + isOneToOne: true + isSetofReturn: false + } + } + test_unnamed_row_scalar: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.test_unnamed_row_scalar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + test_unnamed_row_setof: + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + test_unnamed_view_row: { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + } + Enums: { + meme_status: "new" | "old" | "retired" + user_status: "ACTIVE" | "INACTIVE" + } + CompositeTypes: { + composite_type_with_array_attribute: { + my_text_array: string[] | null + } + composite_type_with_record_attribute: { + todo: Database["public"]["Tables"]["todos"]["Row"] | null + } + } + } + } + + type DatabaseWithoutInternals = Omit + + type DefaultSchema = DatabaseWithoutInternals[Extract] + + export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never, + > = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + + export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, + > = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + + export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, + > = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + + export type Enums< + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof DatabaseWithoutInternals }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never, + > = DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never + + export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof DatabaseWithoutInternals }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never, + > = PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never + + export const Constants = { + public: { + Enums: { + meme_status: ["new", "old", "retired"], + user_status: ["ACTIVE", "INACTIVE"], + }, + }, + } as const + " + ` + ) +}) + +test('typegen: typescript w/ postgrestVersion', async () => { + const { body } = await app.inject({ + method: 'GET', + path: '/generators/typescript', + query: { detect_one_to_one_relationships: 'true', postgrest_version: '13' }, + }) + expect(body).toMatchInlineSnapshot( + ` + "export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + + export type Database = { + // Allows to automatically instantiate createClient with right options + // instead of createClient(URL, KEY) + __InternalSupabase: { + PostgrestVersion: "13" + } + public: { + Tables: { + category: { + Row: { + id: number + name: string + } + Insert: { + id?: number + name: string + } + Update: { + id?: number + name?: string + } + Relationships: [] + } + empty: { + Row: {} + Insert: {} + Update: {} + Relationships: [] + } + events: { + Row: { + created_at: string + data: Json | null + event_type: string | null + id: number + days_since_event: number | null + } + Insert: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Update: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Relationships: [] + } + events_2024: { + Row: { + created_at: string + data: Json | null + event_type: string | null + id: number + } + Insert: { + created_at?: string + data?: Json | null + event_type?: string | null + id: number + } + Update: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Relationships: [] + } + events_2025: { + Row: { + created_at: string + data: Json | null + event_type: string | null + id: number + } + Insert: { + created_at?: string + data?: Json | null + event_type?: string | null + id: number + } + Update: { + created_at?: string + data?: Json | null + event_type?: string | null + id?: number + } + Relationships: [] + } + foreign_table: { + Row: { + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + } + Insert: { + id: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + } + Update: { + id?: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + } + Relationships: [] + } + memes: { + Row: { + category: number | null + created_at: string + id: number + metadata: Json | null + name: string + status: Database["public"]["Enums"]["meme_status"] | null + } + Insert: { + category?: number | null + created_at: string + id?: number + metadata?: Json | null + name: string + status?: Database["public"]["Enums"]["meme_status"] | null + } + Update: { + category?: number | null + created_at?: string + id?: number + metadata?: Json | null + name?: string + status?: Database["public"]["Enums"]["meme_status"] | null + } + Relationships: [ + { + foreignKeyName: "memes_category_fkey" + columns: ["category"] + isOneToOne: false + referencedRelation: "category" + referencedColumns: ["id"] + }, + ] + } + table_with_other_tables_row_type: { + Row: { + col1: Database["public"]["Tables"]["user_details"]["Row"] | null + col2: Database["public"]["Views"]["a_view"]["Row"] | null + } + Insert: { + col1?: Database["public"]["Tables"]["user_details"]["Row"] | null + col2?: Database["public"]["Views"]["a_view"]["Row"] | null + } + Update: { + col1?: Database["public"]["Tables"]["user_details"]["Row"] | null + col2?: Database["public"]["Views"]["a_view"]["Row"] | null + } + Relationships: [] + } + table_with_primary_key_other_than_id: { + Row: { + name: string | null + other_id: number + } + Insert: { + name?: string | null + other_id?: number + } + Update: { + name?: string | null + other_id?: number + } + Relationships: [] + } + todos: { + Row: { + details: string | null + id: number + "user-id": number + blurb: string | null + blurb_varchar: string | null + details_is_long: boolean | null + details_length: number | null + details_words: string[] | null + test_unnamed_row_scalar: number | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null + } + Insert: { + details?: string | null + id?: number + "user-id": number + } + Update: { + details?: string | null + id?: number + "user-id"?: number + } + Relationships: [ + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + user_details: { + Row: { + details: string | null + user_id: number + } + Insert: { + details?: string | null + user_id: number + } + Update: { + details?: string | null + user_id?: number + } + Relationships: [ + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + users: { + Row: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + test_unnamed_row_composite: + | Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null + } + Insert: { + decimal?: number | null + id?: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Update: { + decimal?: number | null + id?: number + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Relationships: [] + } + users_audit: { + Row: { + created_at: string | null + id: number + previous_value: Json | null + user_id: number | null + created_ago: number | null + } + Insert: { + created_at?: string | null + id?: number + previous_value?: Json | null + user_id?: number | null + } + Update: { + created_at?: string | null + id?: number + previous_value?: Json | null + user_id?: number | null + } + Relationships: [] + } + } + Views: { + a_view: { + Row: { + id: number | null + } + Insert: { + id?: number | null + } + Update: { + id?: number | null + } + Relationships: [] + } + todos_matview: { + Row: { + details: string | null + id: number | null + "user-id": number | null + get_todos_by_matview: { + details: string | null + id: number + "user-id": number + } | null + } + Relationships: [ + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + todos_view: { + Row: { + details: string | null + id: number | null + "user-id": number | null + blurb_varchar: string | null + test_unnamed_view_row: { + details: string | null + id: number + "user-id": number + } | null + } + Insert: { + details?: string | null + id?: number | null + "user-id"?: number | null + } + Update: { + details?: string | null + id?: number | null + "user-id"?: number | null + } + Relationships: [ + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "a_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view" + referencedColumns: ["id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["initial_id"] + }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "users_view_with_multiple_refs_to_users" + referencedColumns: ["second_id"] + }, + ] + } + user_todos_summary_view: { + Row: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + Relationships: [] + } + users_view: { + Row: { + decimal: number | null + id: number | null + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + } + Insert: { + decimal?: number | null + id?: number | null + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Update: { + decimal?: number | null + id?: number | null + name?: string | null + status?: Database["public"]["Enums"]["user_status"] | null + user_uuid?: string | null + } + Relationships: [] + } + users_view_with_multiple_refs_to_users: { + Row: { + initial_id: number | null + initial_name: string | null + second_id: number | null + second_name: string | null + } + Relationships: [] + } + } + Functions: { + blurb: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.blurb with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + blurb_varchar: + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + | { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + created_ago: { + Args: { "": Database["public"]["Tables"]["users_audit"]["Row"] } + Returns: { + error: true + } & "the function public.created_ago with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + days_since_event: { + Args: { "": Database["public"]["Tables"]["events"]["Row"] } + Returns: { + error: true + } & "the function public.days_since_event with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + details_is_long: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.details_is_long with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + details_length: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.details_length with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + details_words: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.details_words with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + function_returning_row: { + Args: never + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + } + SetofOptions: { + from: "*" + to: "users" + isOneToOne: true + isSetofReturn: false + } + } + function_returning_set_of_rows: { + Args: never + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + function_returning_single_row: { + Args: { todos: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + } + SetofOptions: { + from: "todos" + to: "users" + isOneToOne: true + isSetofReturn: false + } + } + function_returning_table: { + Args: never + Returns: { + id: number + name: string + }[] + } + function_returning_table_with_args: { + Args: { user_id: number } + Returns: { + id: number + name: string + }[] + } + function_using_setof_rows_one: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: true + } + } + function_using_table_returns: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: false + } + } + get_composite_type_data: { + Args: never + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][] + SetofOptions: { + from: "*" + to: "composite_type_with_array_attribute" + isOneToOne: false + isSetofReturn: true + } + } + get_single_user_summary_from_view: + | { + Args: { search_user_id: number } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "*" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users_view" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + get_todos_by_matview: { + Args: { "": unknown } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "todos_matview" + to: "todos" + isOneToOne: true + isSetofReturn: true + } + } + get_todos_from_user: + | { + Args: { search_user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + get_todos_setof_rows: + | { + Args: { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + get_user_audit_setof_single_row: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + created_at: string | null + id: number + previous_value: Json | null + user_id: number | null + } + SetofOptions: { + from: "users" + to: "users_audit" + isOneToOne: true + isSetofReturn: true + } + } + get_user_ids: { Args: never; Returns: number[] } + get_user_summary: { Args: never; Returns: Record[] } + polymorphic_function: { Args: { "": string }; Returns: undefined } + polymorphic_function_with_different_return: { + Args: { "": string } + Returns: string + } + polymorphic_function_with_no_params_or_unnamed: + | { Args: never; Returns: number } + | { Args: { "": string }; Returns: string } + polymorphic_function_with_unnamed_default: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default(), public.polymorphic_function_with_unnamed_default( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_default_overload: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default_overload(), public.polymorphic_function_with_unnamed_default_overload( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_json: { + Args: { "": Json } + Returns: number + } + polymorphic_function_with_unnamed_jsonb: { + Args: { "": Json } + Returns: number + } + polymorphic_function_with_unnamed_text: { + Args: { "": string } + Returns: number + } + postgres_fdw_disconnect: { Args: { "": string }; Returns: boolean } + postgres_fdw_disconnect_all: { Args: never; Returns: boolean } + postgres_fdw_get_connections: { + Args: never + Returns: Record[] + } + postgres_fdw_handler: { Args: never; Returns: unknown } + postgrest_resolvable_with_override_function: + | { Args: never; Returns: undefined } + | { Args: { a: string }; Returns: number } + | { Args: { b: number }; Returns: string } + | { + Args: { completed: boolean; todo_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_id: number } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + user_uuid: string | null + }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + postgrest_unresolvable_function: + | { Args: never; Returns: undefined } + | { + Args: { a: number } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { + Args: { a: string } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + search_todos_by_details: { + Args: { search_details: string } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + test_internal_query: { Args: never; Returns: undefined } + test_unnamed_row_composite: { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + SetofOptions: { + from: "users" + to: "composite_type_with_array_attribute" + isOneToOne: true + isSetofReturn: false + } + } + test_unnamed_row_scalar: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.test_unnamed_row_scalar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + test_unnamed_row_setof: + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + test_unnamed_view_row: { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + } + Enums: { + meme_status: "new" | "old" | "retired" + user_status: "ACTIVE" | "INACTIVE" + } + CompositeTypes: { + composite_type_with_array_attribute: { + my_text_array: string[] | null + } + composite_type_with_record_attribute: { + todo: Database["public"]["Tables"]["todos"]["Row"] | null + } + } + } + } + + type DatabaseWithoutInternals = Omit + + type DefaultSchema = DatabaseWithoutInternals[Extract] + + export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never, + > = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + + export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, + > = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + + export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, + > = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + + export type Enums< + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof DatabaseWithoutInternals }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never, + > = DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never + + export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof DatabaseWithoutInternals }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never, + > = PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never + + export const Constants = { + public: { + Enums: { + meme_status: ["new", "old", "retired"], + user_status: ["ACTIVE", "INACTIVE"], + }, + }, + } as const + " + ` + ) +}) + +test('typegen: typescript consistent types definitions orders', async () => { + // Helper function to clean up test entities + const cleanupTestEntities = async () => { + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + -- Drop materialized views first (depend on views/tables) + DROP MATERIALIZED VIEW IF EXISTS test_matview_alpha CASCADE; + DROP MATERIALIZED VIEW IF EXISTS test_matview_beta CASCADE; + DROP MATERIALIZED VIEW IF EXISTS test_matview_gamma CASCADE; + + -- Drop views (may depend on tables) + DROP VIEW IF EXISTS test_view_alpha CASCADE; + DROP VIEW IF EXISTS test_view_beta CASCADE; + DROP VIEW IF EXISTS test_view_gamma CASCADE; + + -- Drop functions + DROP FUNCTION IF EXISTS test_func_alpha(integer, text, boolean) CASCADE; + DROP FUNCTION IF EXISTS test_func_beta(integer, text, boolean) CASCADE; + DROP FUNCTION IF EXISTS test_func_gamma(integer, text, boolean) CASCADE; + + -- Alternative signatures for functions (different parameter orders) + DROP FUNCTION IF EXISTS test_func_alpha_2(boolean, text, integer) CASCADE; + DROP FUNCTION IF EXISTS test_func_beta_2(text, boolean, integer) CASCADE; + DROP FUNCTION IF EXISTS test_func_gamma_2(boolean, integer, text) CASCADE; + + -- Drop tables + DROP TABLE IF EXISTS test_table_alpha CASCADE; + DROP TABLE IF EXISTS test_table_beta CASCADE; + DROP TABLE IF EXISTS test_table_gamma CASCADE; + + -- Drop types + DROP TYPE IF EXISTS test_enum_alpha CASCADE; + DROP TYPE IF EXISTS test_enum_beta CASCADE; + `, + }, + }) + } + + // Clean up any existing test entities + await cleanupTestEntities() + + // === FIRST ROUND: Create entities in order A->B->G with property order 1 === + + // Create custom types first + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE TYPE test_enum_alpha AS ENUM ('active', 'inactive', 'pending'); + CREATE TYPE test_enum_beta AS ENUM ('high', 'medium', 'low'); + `, + }, + }) + + // Create tables in order: alpha, beta, gamma with specific column orders + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE TABLE test_table_alpha ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + status test_enum_alpha DEFAULT 'active', + created_at TIMESTAMP DEFAULT NOW() + ); + + CREATE TABLE test_table_beta ( + id SERIAL PRIMARY KEY, + priority test_enum_beta DEFAULT 'medium', + description TEXT, + alpha_id INTEGER REFERENCES test_table_alpha(id) + ); + + CREATE TABLE test_table_gamma ( + id SERIAL PRIMARY KEY, + beta_id INTEGER REFERENCES test_table_beta(id), + value NUMERIC(10,2), + is_active BOOLEAN DEFAULT true + ); + `, + }, + }) + + // Create functions in order: alpha, beta, gamma with specific parameter orders + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE FUNCTION test_func_alpha(param_a integer, param_b text, param_c boolean) + RETURNS integer AS 'SELECT param_a + 1' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_beta(param_a integer, param_b text, param_c boolean) + RETURNS text AS 'SELECT param_b || ''_processed''' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_gamma(param_a integer, param_b text, param_c boolean) + RETURNS boolean AS 'SELECT NOT param_c' LANGUAGE sql IMMUTABLE; + `, + }, + }) + + // Create views in order: alpha, beta, gamma + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE VIEW test_view_alpha AS + SELECT id, name, status, created_at FROM test_table_alpha; + + CREATE VIEW test_view_beta AS + SELECT id, priority, description, alpha_id FROM test_table_beta; + + CREATE VIEW test_view_gamma AS + SELECT id, beta_id, value, is_active FROM test_table_gamma; + `, + }, + }) + + // Create materialized views in order: alpha, beta, gamma + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE MATERIALIZED VIEW test_matview_alpha AS + SELECT id, name, status FROM test_table_alpha; + + CREATE MATERIALIZED VIEW test_matview_beta AS + SELECT id, priority, description FROM test_table_beta; + + CREATE MATERIALIZED VIEW test_matview_gamma AS + SELECT id, value, is_active FROM test_table_gamma; + `, + }, + }) + + // Generate types for first configuration + const { body: firstCall } = await app.inject({ + method: 'GET', + path: '/generators/typescript', + query: { detect_one_to_one_relationships: 'true', postgrest_version: '13' }, + }) + + // === SECOND ROUND: Drop and recreate in reverse order G->B->A with different property orders === + + // Clean up all test entities + await cleanupTestEntities() + + // Create custom types in reverse order but keep the enum internal ordering (typegen is rightfully dependent on the enum order) + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE TYPE test_enum_beta AS ENUM ('high', 'medium', 'low'); + CREATE TYPE test_enum_alpha AS ENUM ('active', 'inactive', 'pending'); + `, + }, + }) + + // Create tables in reverse order: gamma, beta, alpha with different column orders + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE TABLE test_table_alpha ( + created_at TIMESTAMP DEFAULT NOW(), + status test_enum_alpha DEFAULT 'active', + name TEXT NOT NULL, + id SERIAL PRIMARY KEY + ); + + CREATE TABLE test_table_beta ( + alpha_id INTEGER REFERENCES test_table_alpha(id), + description TEXT, + priority test_enum_beta DEFAULT 'medium', + id SERIAL PRIMARY KEY + ); + + CREATE TABLE test_table_gamma ( + is_active BOOLEAN DEFAULT true, + value NUMERIC(10,2), + beta_id INTEGER REFERENCES test_table_beta(id), + id SERIAL PRIMARY KEY + ); + `, + }, + }) + + // Create functions in reverse order: gamma, beta, alpha with same parameter orders + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE FUNCTION test_func_gamma(param_a integer, param_b text, param_c boolean) + RETURNS boolean AS 'SELECT NOT param_c' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_beta(param_a integer, param_b text, param_c boolean) + RETURNS text AS 'SELECT param_b || ''_processed''' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_alpha(param_a integer, param_b text, param_c boolean) + RETURNS integer AS 'SELECT param_a + 1' LANGUAGE sql IMMUTABLE; + `, + }, + }) + + // Create views in reverse order: gamma, beta, alpha + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE VIEW test_view_gamma AS + SELECT is_active, value, beta_id, id FROM test_table_gamma; + + CREATE VIEW test_view_beta AS + SELECT alpha_id, description, priority, id FROM test_table_beta; + + CREATE VIEW test_view_alpha AS + SELECT created_at, status, name, id FROM test_table_alpha; + `, + }, + }) + + // Create materialized views in reverse order: gamma, beta, alpha + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE MATERIALIZED VIEW test_matview_gamma AS + SELECT is_active, value, id FROM test_table_gamma; + + CREATE MATERIALIZED VIEW test_matview_beta AS + SELECT description, priority, id FROM test_table_beta; + + CREATE MATERIALIZED VIEW test_matview_alpha AS + SELECT status, name, id FROM test_table_alpha; + `, + }, + }) + + // Generate types for second configuration + const { body: secondCall } = await app.inject({ + method: 'GET', + path: '/generators/typescript', + query: { detect_one_to_one_relationships: 'true', postgrest_version: '13' }, + }) + + // Clean up test entities + await cleanupTestEntities() + + // The generated types should be identical regardless of: + // 1. Entity creation order (alpha->beta->gamma vs gamma->beta->alpha) + // 2. Property declaration order (columns, function parameters) + // 3. Enum value order + expect(firstCall).toEqual(secondCall) +}) + +test('typegen: typescript function override order stability', async () => { + // Helper function to clean up test entities + const cleanupTestEntities = async () => { + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + -- Drop functions with all possible signatures + DROP FUNCTION IF EXISTS test_func_override(integer, text) CASCADE; + DROP FUNCTION IF EXISTS test_func_override(text, integer) CASCADE; + DROP FUNCTION IF EXISTS test_func_override(boolean, integer, text) CASCADE; + DROP FUNCTION IF EXISTS test_func_override(text, boolean) CASCADE; + `, + }, + }) + } + + // Clean up any existing test entities + await cleanupTestEntities() + + // === FIRST ROUND: Create function overrides in order 1 === + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + -- Create function overrides in specific order + CREATE FUNCTION test_func_override(param_a integer, param_b text) + RETURNS integer AS 'SELECT param_a + 1' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_override(param_a text, param_b integer) + RETURNS text AS 'SELECT param_a || param_b::text' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_override(param_a boolean, param_b integer, param_c text) + RETURNS boolean AS 'SELECT param_a' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_override(param_a text, param_b boolean) + RETURNS text AS 'SELECT CASE WHEN param_b THEN param_a ELSE '''' END' LANGUAGE sql IMMUTABLE; + `, + }, + }) + + // Generate types for first configuration + const { body: firstCall } = await app.inject({ + method: 'GET', + path: '/generators/typescript', + query: { detect_one_to_one_relationships: 'true', postgrest_version: '13' }, + }) + + // === SECOND ROUND: Modify function definitions without changing signatures === + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + -- Modify function definitions (using CREATE OR REPLACE) + -- This should preserve the order + CREATE OR REPLACE FUNCTION test_func_override(param_a integer, param_b text) + RETURNS integer AS 'SELECT param_a + 100' LANGUAGE sql IMMUTABLE; + + CREATE OR REPLACE FUNCTION test_func_override(param_a text, param_b integer) + RETURNS text AS 'SELECT param_a || ''_'' || param_b::text' LANGUAGE sql IMMUTABLE; + + CREATE OR REPLACE FUNCTION test_func_override(param_a boolean, param_b integer, param_c text) + RETURNS boolean AS 'SELECT NOT param_a' LANGUAGE sql IMMUTABLE; + + CREATE OR REPLACE FUNCTION test_func_override(param_a text, param_b boolean) + RETURNS text AS 'SELECT CASE WHEN param_b THEN param_a || ''_true'' ELSE ''false'' END' LANGUAGE sql IMMUTABLE; + `, + }, + }) + + // Generate types for second configuration (after modifying definitions) + const { body: secondCall } = await app.inject({ + method: 'GET', + path: '/generators/typescript', + query: { detect_one_to_one_relationships: 'true', postgrest_version: '13' }, + }) + + // === THIRD ROUND: Drop and recreate in different order === + await cleanupTestEntities() + + // Create functions in reverse order + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + -- Create function overrides in reverse order + CREATE FUNCTION test_func_override(param_a text, param_b boolean) + RETURNS text AS 'SELECT CASE WHEN param_b THEN param_a ELSE '''' END' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_override(param_a boolean, param_b integer, param_c text) + RETURNS boolean AS 'SELECT param_a' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_override(param_a text, param_b integer) + RETURNS text AS 'SELECT param_a || param_b::text' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_override(param_a integer, param_b text) + RETURNS integer AS 'SELECT param_a + 1' LANGUAGE sql IMMUTABLE; + `, + }, + }) + + // Generate types for third configuration (recreated in different order) + const { body: thirdCall } = await app.inject({ + method: 'GET', + path: '/generators/typescript', + query: { detect_one_to_one_relationships: 'true', postgrest_version: '13' }, + }) + + // Clean up test entities + await cleanupTestEntities() + + expect(firstCall).toEqual(secondCall) + expect(secondCall).toEqual(thirdCall) +}) + +test('typegen: go', async () => { + const { body } = await app.inject({ method: 'GET', path: '/generators/go' }) + expect(body).toMatchInlineSnapshot(` + "package database + + type PublicUsersSelect struct { + Decimal *float64 \`json:"decimal"\` + Id int64 \`json:"id"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + UserUuid *string \`json:"user_uuid"\` + } + + type PublicUsersInsert struct { + Decimal *float64 \`json:"decimal"\` + Id *int64 \`json:"id"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + UserUuid *string \`json:"user_uuid"\` + } + + type PublicUsersUpdate struct { + Decimal *float64 \`json:"decimal"\` + Id *int64 \`json:"id"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + UserUuid *string \`json:"user_uuid"\` + } + + type PublicTodosSelect struct { + Details *string \`json:"details"\` + Id int64 \`json:"id"\` + UserId int64 \`json:"user-id"\` + } + + type PublicTodosInsert struct { + Details *string \`json:"details"\` + Id *int64 \`json:"id"\` + UserId int64 \`json:"user-id"\` + } + + type PublicTodosUpdate struct { + Details *string \`json:"details"\` + Id *int64 \`json:"id"\` + UserId *int64 \`json:"user-id"\` + } + + type PublicUsersAuditSelect struct { + CreatedAt *string \`json:"created_at"\` + Id int64 \`json:"id"\` + PreviousValue interface{} \`json:"previous_value"\` + UserId *int64 \`json:"user_id"\` + } + + type PublicUsersAuditInsert struct { + CreatedAt *string \`json:"created_at"\` + Id *int64 \`json:"id"\` + PreviousValue interface{} \`json:"previous_value"\` + UserId *int64 \`json:"user_id"\` + } + + type PublicUsersAuditUpdate struct { + CreatedAt *string \`json:"created_at"\` + Id *int64 \`json:"id"\` + PreviousValue interface{} \`json:"previous_value"\` + UserId *int64 \`json:"user_id"\` + } + + type PublicUserDetailsSelect struct { + Details *string \`json:"details"\` + UserId int64 \`json:"user_id"\` + } + + type PublicUserDetailsInsert struct { + Details *string \`json:"details"\` + UserId int64 \`json:"user_id"\` + } + + type PublicUserDetailsUpdate struct { + Details *string \`json:"details"\` + UserId *int64 \`json:"user_id"\` + } + + type PublicEmptySelect struct { + + } + + type PublicEmptyInsert struct { + + } + + type PublicEmptyUpdate struct { + + } + + type PublicTableWithOtherTablesRowTypeSelect struct { + Col1 interface{} \`json:"col1"\` + Col2 interface{} \`json:"col2"\` + } + + type PublicTableWithOtherTablesRowTypeInsert struct { + Col1 interface{} \`json:"col1"\` + Col2 interface{} \`json:"col2"\` + } + + type PublicTableWithOtherTablesRowTypeUpdate struct { + Col1 interface{} \`json:"col1"\` + Col2 interface{} \`json:"col2"\` + } + + type PublicTableWithPrimaryKeyOtherThanIdSelect struct { + Name *string \`json:"name"\` + OtherId int64 \`json:"other_id"\` + } + + type PublicTableWithPrimaryKeyOtherThanIdInsert struct { + Name *string \`json:"name"\` + OtherId *int64 \`json:"other_id"\` + } + + type PublicTableWithPrimaryKeyOtherThanIdUpdate struct { + Name *string \`json:"name"\` + OtherId *int64 \`json:"other_id"\` + } + + type PublicEventsSelect struct { + CreatedAt string \`json:"created_at"\` + Data interface{} \`json:"data"\` + EventType *string \`json:"event_type"\` + Id int64 \`json:"id"\` + } + + type PublicEventsInsert struct { + CreatedAt *string \`json:"created_at"\` + Data interface{} \`json:"data"\` + EventType *string \`json:"event_type"\` + Id *int64 \`json:"id"\` + } + + type PublicEventsUpdate struct { + CreatedAt *string \`json:"created_at"\` + Data interface{} \`json:"data"\` + EventType *string \`json:"event_type"\` + Id *int64 \`json:"id"\` + } + + type PublicEvents2024Select struct { + CreatedAt string \`json:"created_at"\` + Data interface{} \`json:"data"\` + EventType *string \`json:"event_type"\` + Id int64 \`json:"id"\` + } + + type PublicEvents2024Insert struct { + CreatedAt *string \`json:"created_at"\` + Data interface{} \`json:"data"\` + EventType *string \`json:"event_type"\` + Id int64 \`json:"id"\` + } + + type PublicEvents2024Update struct { + CreatedAt *string \`json:"created_at"\` + Data interface{} \`json:"data"\` + EventType *string \`json:"event_type"\` + Id *int64 \`json:"id"\` + } + + type PublicEvents2025Select struct { + CreatedAt string \`json:"created_at"\` + Data interface{} \`json:"data"\` + EventType *string \`json:"event_type"\` + Id int64 \`json:"id"\` + } + + type PublicEvents2025Insert struct { + CreatedAt *string \`json:"created_at"\` + Data interface{} \`json:"data"\` + EventType *string \`json:"event_type"\` + Id int64 \`json:"id"\` + } + + type PublicEvents2025Update struct { + CreatedAt *string \`json:"created_at"\` + Data interface{} \`json:"data"\` + EventType *string \`json:"event_type"\` + Id *int64 \`json:"id"\` + } + + type PublicCategorySelect struct { + Id int32 \`json:"id"\` + Name string \`json:"name"\` + } + + type PublicCategoryInsert struct { + Id *int32 \`json:"id"\` + Name string \`json:"name"\` + } + + type PublicCategoryUpdate struct { + Id *int32 \`json:"id"\` + Name *string \`json:"name"\` + } + + type PublicMemesSelect struct { + Category *int32 \`json:"category"\` + CreatedAt string \`json:"created_at"\` + Id int32 \`json:"id"\` + Metadata interface{} \`json:"metadata"\` + Name string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicMemesInsert struct { + Category *int32 \`json:"category"\` + CreatedAt string \`json:"created_at"\` + Id *int32 \`json:"id"\` + Metadata interface{} \`json:"metadata"\` + Name string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicMemesUpdate struct { + Category *int32 \`json:"category"\` + CreatedAt *string \`json:"created_at"\` + Id *int32 \`json:"id"\` + Metadata interface{} \`json:"metadata"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicAViewSelect struct { + Id *int64 \`json:"id"\` + } + + type PublicTodosViewSelect struct { + Details *string \`json:"details"\` + Id *int64 \`json:"id"\` + UserId *int64 \`json:"user-id"\` + } + + type PublicUsersViewSelect struct { + Decimal *float64 \`json:"decimal"\` + Id *int64 \`json:"id"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + UserUuid *string \`json:"user_uuid"\` + } + + type PublicUserTodosSummaryViewSelect struct { + TodoCount *int64 \`json:"todo_count"\` + TodoDetails []*string \`json:"todo_details"\` + UserId *int64 \`json:"user_id"\` + UserName *string \`json:"user_name"\` + UserStatus *string \`json:"user_status"\` + } + + type PublicUsersViewWithMultipleRefsToUsersSelect struct { + InitialId *int64 \`json:"initial_id"\` + InitialName *string \`json:"initial_name"\` + SecondId *int64 \`json:"second_id"\` + SecondName *string \`json:"second_name"\` + } + + type PublicTodosMatviewSelect struct { + Details *string \`json:"details"\` + Id *int64 \`json:"id"\` + UserId *int64 \`json:"user-id"\` + } + + type PublicCompositeTypeWithArrayAttribute struct { + MyTextArray interface{} \`json:"my_text_array"\` + } + + type PublicCompositeTypeWithRecordAttribute struct { + Todo interface{} \`json:"todo"\` + }" + `) +}) + +test('typegen: swift', async () => { + const { body } = await app.inject({ method: 'GET', path: '/generators/swift' }) + expect(body).toMatchInlineSnapshot(` + "import Foundation + import Supabase + + internal enum PublicSchema { + internal enum MemeStatus: String, Codable, Hashable, Sendable { + case new = "new" + case old = "old" + case retired = "retired" + } + internal enum UserStatus: String, Codable, Hashable, Sendable { + case active = "ACTIVE" + case inactive = "INACTIVE" + } + internal struct CategorySelect: Codable, Hashable, Sendable { + internal let id: Int32 + internal let name: String + internal enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + } + } + internal struct CategoryInsert: Codable, Hashable, Sendable { + internal let id: Int32? + internal let name: String + internal enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + } + } + internal struct CategoryUpdate: Codable, Hashable, Sendable { + internal let id: Int32? + internal let name: String? + internal enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + } + } + internal struct EmptySelect: Codable, Hashable, Sendable { + } + internal struct EmptyInsert: Codable, Hashable, Sendable { + } + internal struct EmptyUpdate: Codable, Hashable, Sendable { + } + internal struct EventsSelect: Codable, Hashable, Sendable, Identifiable { + internal let createdAt: String + internal let data: AnyJSON? + internal let eventType: String? + internal let id: Int64 + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + internal struct EventsInsert: Codable, Hashable, Sendable, Identifiable { + internal let createdAt: String? + internal let data: AnyJSON? + internal let eventType: String? + internal let id: Int64? + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + internal struct EventsUpdate: Codable, Hashable, Sendable, Identifiable { + internal let createdAt: String? + internal let data: AnyJSON? + internal let eventType: String? + internal let id: Int64? + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + internal struct Events2024Select: Codable, Hashable, Sendable { + internal let createdAt: String + internal let data: AnyJSON? + internal let eventType: String? + internal let id: Int64 + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + internal struct Events2024Insert: Codable, Hashable, Sendable { + internal let createdAt: String? + internal let data: AnyJSON? + internal let eventType: String? + internal let id: Int64 + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + internal struct Events2024Update: Codable, Hashable, Sendable { + internal let createdAt: String? + internal let data: AnyJSON? + internal let eventType: String? + internal let id: Int64? + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + internal struct Events2025Select: Codable, Hashable, Sendable { + internal let createdAt: String + internal let data: AnyJSON? + internal let eventType: String? + internal let id: Int64 + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + internal struct Events2025Insert: Codable, Hashable, Sendable { + internal let createdAt: String? + internal let data: AnyJSON? + internal let eventType: String? + internal let id: Int64 + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + internal struct Events2025Update: Codable, Hashable, Sendable { + internal let createdAt: String? + internal let data: AnyJSON? + internal let eventType: String? + internal let id: Int64? + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + internal struct ForeignTableSelect: Codable, Hashable, Sendable { + internal let id: Int64 + internal let name: String? + internal let status: UserStatus? + internal enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + internal struct ForeignTableInsert: Codable, Hashable, Sendable { + internal let id: Int64 + internal let name: String? + internal let status: UserStatus? + internal enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + internal struct ForeignTableUpdate: Codable, Hashable, Sendable { + internal let id: Int64? + internal let name: String? + internal let status: UserStatus? + internal enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + internal struct MemesSelect: Codable, Hashable, Sendable { + internal let category: Int32? + internal let createdAt: String + internal let id: Int32 + internal let metadata: AnyJSON? + internal let name: String + internal let status: MemeStatus? + internal enum CodingKeys: String, CodingKey { + case category = "category" + case createdAt = "created_at" + case id = "id" + case metadata = "metadata" + case name = "name" + case status = "status" + } + } + internal struct MemesInsert: Codable, Hashable, Sendable { + internal let category: Int32? + internal let createdAt: String + internal let id: Int32? + internal let metadata: AnyJSON? + internal let name: String + internal let status: MemeStatus? + internal enum CodingKeys: String, CodingKey { + case category = "category" + case createdAt = "created_at" + case id = "id" + case metadata = "metadata" + case name = "name" + case status = "status" + } + } + internal struct MemesUpdate: Codable, Hashable, Sendable { + internal let category: Int32? + internal let createdAt: String? + internal let id: Int32? + internal let metadata: AnyJSON? + internal let name: String? + internal let status: MemeStatus? + internal enum CodingKeys: String, CodingKey { + case category = "category" + case createdAt = "created_at" + case id = "id" + case metadata = "metadata" + case name = "name" + case status = "status" + } + } + internal struct TableWithOtherTablesRowTypeSelect: Codable, Hashable, Sendable { + internal let col1: UserDetailsSelect? + internal let col2: AViewSelect? + internal enum CodingKeys: String, CodingKey { + case col1 = "col1" + case col2 = "col2" + } + } + internal struct TableWithOtherTablesRowTypeInsert: Codable, Hashable, Sendable { + internal let col1: UserDetailsSelect? + internal let col2: AViewSelect? + internal enum CodingKeys: String, CodingKey { + case col1 = "col1" + case col2 = "col2" + } + } + internal struct TableWithOtherTablesRowTypeUpdate: Codable, Hashable, Sendable { + internal let col1: UserDetailsSelect? + internal let col2: AViewSelect? + internal enum CodingKeys: String, CodingKey { + case col1 = "col1" + case col2 = "col2" + } + } + internal struct TableWithPrimaryKeyOtherThanIdSelect: Codable, Hashable, Sendable, Identifiable { + internal var id: Int64 { otherId } + internal let name: String? + internal let otherId: Int64 + internal enum CodingKeys: String, CodingKey { + case name = "name" + case otherId = "other_id" + } + } + internal struct TableWithPrimaryKeyOtherThanIdInsert: Codable, Hashable, Sendable, Identifiable { + internal var id: Int64? { otherId } + internal let name: String? + internal let otherId: Int64? + internal enum CodingKeys: String, CodingKey { + case name = "name" + case otherId = "other_id" + } + } + internal struct TableWithPrimaryKeyOtherThanIdUpdate: Codable, Hashable, Sendable, Identifiable { + internal var id: Int64? { otherId } + internal let name: String? + internal let otherId: Int64? + internal enum CodingKeys: String, CodingKey { + case name = "name" + case otherId = "other_id" + } + } + internal struct TodosSelect: Codable, Hashable, Sendable, Identifiable { + internal let details: String? + internal let id: Int64 + internal let userId: Int64 + internal enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + internal struct TodosInsert: Codable, Hashable, Sendable, Identifiable { + internal let details: String? + internal let id: Int64? + internal let userId: Int64 + internal enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + internal struct TodosUpdate: Codable, Hashable, Sendable, Identifiable { + internal let details: String? + internal let id: Int64? + internal let userId: Int64? + internal enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + internal struct UserDetailsSelect: Codable, Hashable, Sendable { + internal let details: String? + internal let userId: Int64 + internal enum CodingKeys: String, CodingKey { + case details = "details" + case userId = "user_id" + } + } + internal struct UserDetailsInsert: Codable, Hashable, Sendable { + internal let details: String? + internal let userId: Int64 + internal enum CodingKeys: String, CodingKey { + case details = "details" + case userId = "user_id" + } + } + internal struct UserDetailsUpdate: Codable, Hashable, Sendable { + internal let details: String? + internal let userId: Int64? + internal enum CodingKeys: String, CodingKey { + case details = "details" + case userId = "user_id" + } + } + internal struct UsersSelect: Codable, Hashable, Sendable, Identifiable { + internal let decimal: Decimal? + internal let id: Int64 + internal let name: String? + internal let status: UserStatus? + internal let userUuid: UUID? + internal enum CodingKeys: String, CodingKey { + case decimal = "decimal" + case id = "id" + case name = "name" + case status = "status" + case userUuid = "user_uuid" + } + } + internal struct UsersInsert: Codable, Hashable, Sendable, Identifiable { + internal let decimal: Decimal? + internal let id: Int64? + internal let name: String? + internal let status: UserStatus? + internal let userUuid: UUID? + internal enum CodingKeys: String, CodingKey { + case decimal = "decimal" + case id = "id" + case name = "name" + case status = "status" + case userUuid = "user_uuid" + } + } + internal struct UsersUpdate: Codable, Hashable, Sendable, Identifiable { + internal let decimal: Decimal? + internal let id: Int64? + internal let name: String? + internal let status: UserStatus? + internal let userUuid: UUID? + internal enum CodingKeys: String, CodingKey { + case decimal = "decimal" + case id = "id" + case name = "name" + case status = "status" + case userUuid = "user_uuid" + } + } + internal struct UsersAuditSelect: Codable, Hashable, Sendable, Identifiable { + internal let createdAt: String? + internal let id: Int64 + internal let previousValue: AnyJSON? + internal let userId: Int64? + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case id = "id" + case previousValue = "previous_value" + case userId = "user_id" + } + } + internal struct UsersAuditInsert: Codable, Hashable, Sendable, Identifiable { + internal let createdAt: String? + internal let id: Int64? + internal let previousValue: AnyJSON? + internal let userId: Int64? + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case id = "id" + case previousValue = "previous_value" + case userId = "user_id" + } + } + internal struct UsersAuditUpdate: Codable, Hashable, Sendable, Identifiable { + internal let createdAt: String? + internal let id: Int64? + internal let previousValue: AnyJSON? + internal let userId: Int64? + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case id = "id" + case previousValue = "previous_value" + case userId = "user_id" + } + } + internal struct AViewSelect: Codable, Hashable, Sendable { + internal let id: Int64? + internal enum CodingKeys: String, CodingKey { + case id = "id" + } + } + internal struct TodosMatviewSelect: Codable, Hashable, Sendable { + internal let details: String? + internal let id: Int64? + internal let userId: Int64? + internal enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + internal struct TodosViewSelect: Codable, Hashable, Sendable { + internal let details: String? + internal let id: Int64? + internal let userId: Int64? + internal enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + internal struct UserTodosSummaryViewSelect: Codable, Hashable, Sendable { + internal let todoCount: Int64? + internal let todoDetails: [String]? + internal let userId: Int64? + internal let userName: String? + internal let userStatus: UserStatus? + internal enum CodingKeys: String, CodingKey { + case todoCount = "todo_count" + case todoDetails = "todo_details" + case userId = "user_id" + case userName = "user_name" + case userStatus = "user_status" + } + } + internal struct UsersViewSelect: Codable, Hashable, Sendable { + internal let decimal: Decimal? + internal let id: Int64? + internal let name: String? + internal let status: UserStatus? + internal let userUuid: UUID? + internal enum CodingKeys: String, CodingKey { + case decimal = "decimal" + case id = "id" + case name = "name" + case status = "status" + case userUuid = "user_uuid" + } + } + internal struct UsersViewWithMultipleRefsToUsersSelect: Codable, Hashable, Sendable { + internal let initialId: Int64? + internal let initialName: String? + internal let secondId: Int64? + internal let secondName: String? + internal enum CodingKeys: String, CodingKey { + case initialId = "initial_id" + case initialName = "initial_name" + case secondId = "second_id" + case secondName = "second_name" + } + } + internal struct CompositeTypeWithArrayAttribute: Codable, Hashable, Sendable { + internal let MyTextArray: AnyJSON + internal enum CodingKeys: String, CodingKey { + case MyTextArray = "my_text_array" + } + } + internal struct CompositeTypeWithRecordAttribute: Codable, Hashable, Sendable { + internal let Todo: TodosSelect + internal enum CodingKeys: String, CodingKey { + case Todo = "todo" + } + } + }" + `) +}) + +test('typegen: swift w/ public access control', async () => { + const { body } = await app.inject({ + method: 'GET', + path: '/generators/swift', + query: { access_control: 'public' }, + }) + expect(body).toMatchInlineSnapshot(` + "import Foundation + import Supabase + + public enum PublicSchema { + public enum MemeStatus: String, Codable, Hashable, Sendable { + case new = "new" + case old = "old" + case retired = "retired" + } + public enum UserStatus: String, Codable, Hashable, Sendable { + case active = "ACTIVE" + case inactive = "INACTIVE" + } + public struct CategorySelect: Codable, Hashable, Sendable { + public let id: Int32 + public let name: String + public enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + } + } + public struct CategoryInsert: Codable, Hashable, Sendable { + public let id: Int32? + public let name: String + public enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + } + } + public struct CategoryUpdate: Codable, Hashable, Sendable { + public let id: Int32? + public let name: String? + public enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + } + } + public struct EmptySelect: Codable, Hashable, Sendable { + } + public struct EmptyInsert: Codable, Hashable, Sendable { + } + public struct EmptyUpdate: Codable, Hashable, Sendable { + } + public struct EventsSelect: Codable, Hashable, Sendable, Identifiable { + public let createdAt: String + public let data: AnyJSON? + public let eventType: String? + public let id: Int64 + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + public struct EventsInsert: Codable, Hashable, Sendable, Identifiable { + public let createdAt: String? + public let data: AnyJSON? + public let eventType: String? + public let id: Int64? + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + public struct EventsUpdate: Codable, Hashable, Sendable, Identifiable { + public let createdAt: String? + public let data: AnyJSON? + public let eventType: String? + public let id: Int64? + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + public struct Events2024Select: Codable, Hashable, Sendable { + public let createdAt: String + public let data: AnyJSON? + public let eventType: String? + public let id: Int64 + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + public struct Events2024Insert: Codable, Hashable, Sendable { + public let createdAt: String? + public let data: AnyJSON? + public let eventType: String? + public let id: Int64 + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + public struct Events2024Update: Codable, Hashable, Sendable { + public let createdAt: String? + public let data: AnyJSON? + public let eventType: String? + public let id: Int64? + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + public struct Events2025Select: Codable, Hashable, Sendable { + public let createdAt: String + public let data: AnyJSON? + public let eventType: String? + public let id: Int64 + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + public struct Events2025Insert: Codable, Hashable, Sendable { + public let createdAt: String? + public let data: AnyJSON? + public let eventType: String? + public let id: Int64 + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + public struct Events2025Update: Codable, Hashable, Sendable { + public let createdAt: String? + public let data: AnyJSON? + public let eventType: String? + public let id: Int64? + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case data = "data" + case eventType = "event_type" + case id = "id" + } + } + public struct ForeignTableSelect: Codable, Hashable, Sendable { + public let id: Int64 + public let name: String? + public let status: UserStatus? + public enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + public struct ForeignTableInsert: Codable, Hashable, Sendable { + public let id: Int64 + public let name: String? + public let status: UserStatus? + public enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + public struct ForeignTableUpdate: Codable, Hashable, Sendable { + public let id: Int64? + public let name: String? + public let status: UserStatus? + public enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + public struct MemesSelect: Codable, Hashable, Sendable { + public let category: Int32? + public let createdAt: String + public let id: Int32 + public let metadata: AnyJSON? + public let name: String + public let status: MemeStatus? + public enum CodingKeys: String, CodingKey { + case category = "category" + case createdAt = "created_at" + case id = "id" + case metadata = "metadata" + case name = "name" + case status = "status" + } + } + public struct MemesInsert: Codable, Hashable, Sendable { + public let category: Int32? + public let createdAt: String + public let id: Int32? + public let metadata: AnyJSON? + public let name: String + public let status: MemeStatus? + public enum CodingKeys: String, CodingKey { + case category = "category" + case createdAt = "created_at" + case id = "id" + case metadata = "metadata" + case name = "name" + case status = "status" + } + } + public struct MemesUpdate: Codable, Hashable, Sendable { + public let category: Int32? + public let createdAt: String? + public let id: Int32? + public let metadata: AnyJSON? + public let name: String? + public let status: MemeStatus? + public enum CodingKeys: String, CodingKey { + case category = "category" + case createdAt = "created_at" + case id = "id" + case metadata = "metadata" + case name = "name" + case status = "status" + } + } + public struct TableWithOtherTablesRowTypeSelect: Codable, Hashable, Sendable { + public let col1: UserDetailsSelect? + public let col2: AViewSelect? + public enum CodingKeys: String, CodingKey { + case col1 = "col1" + case col2 = "col2" + } + } + public struct TableWithOtherTablesRowTypeInsert: Codable, Hashable, Sendable { + public let col1: UserDetailsSelect? + public let col2: AViewSelect? + public enum CodingKeys: String, CodingKey { + case col1 = "col1" + case col2 = "col2" + } + } + public struct TableWithOtherTablesRowTypeUpdate: Codable, Hashable, Sendable { + public let col1: UserDetailsSelect? + public let col2: AViewSelect? + public enum CodingKeys: String, CodingKey { + case col1 = "col1" + case col2 = "col2" + } + } + public struct TableWithPrimaryKeyOtherThanIdSelect: Codable, Hashable, Sendable, Identifiable { + public var id: Int64 { otherId } + public let name: String? + public let otherId: Int64 + public enum CodingKeys: String, CodingKey { + case name = "name" + case otherId = "other_id" + } + } + public struct TableWithPrimaryKeyOtherThanIdInsert: Codable, Hashable, Sendable, Identifiable { + public var id: Int64? { otherId } + public let name: String? + public let otherId: Int64? + public enum CodingKeys: String, CodingKey { + case name = "name" + case otherId = "other_id" + } + } + public struct TableWithPrimaryKeyOtherThanIdUpdate: Codable, Hashable, Sendable, Identifiable { + public var id: Int64? { otherId } + public let name: String? + public let otherId: Int64? + public enum CodingKeys: String, CodingKey { + case name = "name" + case otherId = "other_id" + } + } + public struct TodosSelect: Codable, Hashable, Sendable, Identifiable { + public let details: String? + public let id: Int64 + public let userId: Int64 + public enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + public struct TodosInsert: Codable, Hashable, Sendable, Identifiable { + public let details: String? + public let id: Int64? + public let userId: Int64 + public enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + public struct TodosUpdate: Codable, Hashable, Sendable, Identifiable { + public let details: String? + public let id: Int64? + public let userId: Int64? + public enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + public struct UserDetailsSelect: Codable, Hashable, Sendable { + public let details: String? + public let userId: Int64 + public enum CodingKeys: String, CodingKey { + case details = "details" + case userId = "user_id" + } + } + public struct UserDetailsInsert: Codable, Hashable, Sendable { + public let details: String? + public let userId: Int64 + public enum CodingKeys: String, CodingKey { + case details = "details" + case userId = "user_id" + } + } + public struct UserDetailsUpdate: Codable, Hashable, Sendable { + public let details: String? + public let userId: Int64? + public enum CodingKeys: String, CodingKey { + case details = "details" + case userId = "user_id" + } + } + public struct UsersSelect: Codable, Hashable, Sendable, Identifiable { + public let decimal: Decimal? + public let id: Int64 + public let name: String? + public let status: UserStatus? + public let userUuid: UUID? + public enum CodingKeys: String, CodingKey { + case decimal = "decimal" + case id = "id" + case name = "name" + case status = "status" + case userUuid = "user_uuid" + } + } + public struct UsersInsert: Codable, Hashable, Sendable, Identifiable { + public let decimal: Decimal? + public let id: Int64? + public let name: String? + public let status: UserStatus? + public let userUuid: UUID? + public enum CodingKeys: String, CodingKey { + case decimal = "decimal" + case id = "id" + case name = "name" + case status = "status" + case userUuid = "user_uuid" + } + } + public struct UsersUpdate: Codable, Hashable, Sendable, Identifiable { + public let decimal: Decimal? + public let id: Int64? + public let name: String? + public let status: UserStatus? + public let userUuid: UUID? + public enum CodingKeys: String, CodingKey { + case decimal = "decimal" + case id = "id" + case name = "name" + case status = "status" + case userUuid = "user_uuid" + } + } + public struct UsersAuditSelect: Codable, Hashable, Sendable, Identifiable { + public let createdAt: String? + public let id: Int64 + public let previousValue: AnyJSON? + public let userId: Int64? + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case id = "id" + case previousValue = "previous_value" + case userId = "user_id" + } + } + public struct UsersAuditInsert: Codable, Hashable, Sendable, Identifiable { + public let createdAt: String? + public let id: Int64? + public let previousValue: AnyJSON? + public let userId: Int64? + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case id = "id" + case previousValue = "previous_value" + case userId = "user_id" + } + } + public struct UsersAuditUpdate: Codable, Hashable, Sendable, Identifiable { + public let createdAt: String? + public let id: Int64? + public let previousValue: AnyJSON? + public let userId: Int64? + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case id = "id" + case previousValue = "previous_value" + case userId = "user_id" + } + } + public struct AViewSelect: Codable, Hashable, Sendable { + public let id: Int64? + public enum CodingKeys: String, CodingKey { + case id = "id" + } + } + public struct TodosMatviewSelect: Codable, Hashable, Sendable { + public let details: String? + public let id: Int64? + public let userId: Int64? + public enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + public struct TodosViewSelect: Codable, Hashable, Sendable { + public let details: String? + public let id: Int64? + public let userId: Int64? + public enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + public struct UserTodosSummaryViewSelect: Codable, Hashable, Sendable { + public let todoCount: Int64? + public let todoDetails: [String]? + public let userId: Int64? + public let userName: String? + public let userStatus: UserStatus? + public enum CodingKeys: String, CodingKey { + case todoCount = "todo_count" + case todoDetails = "todo_details" + case userId = "user_id" + case userName = "user_name" + case userStatus = "user_status" + } + } + public struct UsersViewSelect: Codable, Hashable, Sendable { + public let decimal: Decimal? + public let id: Int64? + public let name: String? + public let status: UserStatus? + public let userUuid: UUID? + public enum CodingKeys: String, CodingKey { + case decimal = "decimal" + case id = "id" + case name = "name" + case status = "status" + case userUuid = "user_uuid" + } + } + public struct UsersViewWithMultipleRefsToUsersSelect: Codable, Hashable, Sendable { + public let initialId: Int64? + public let initialName: String? + public let secondId: Int64? + public let secondName: String? + public enum CodingKeys: String, CodingKey { + case initialId = "initial_id" + case initialName = "initial_name" + case secondId = "second_id" + case secondName = "second_name" + } + } + public struct CompositeTypeWithArrayAttribute: Codable, Hashable, Sendable { + public let MyTextArray: AnyJSON + public enum CodingKeys: String, CodingKey { + case MyTextArray = "my_text_array" + } + } + public struct CompositeTypeWithRecordAttribute: Codable, Hashable, Sendable { + public let Todo: TodosSelect + public enum CodingKeys: String, CodingKey { + case Todo = "todo" + } + } + }" + `) +}) + +test('typegen: python', async () => { + const { body } = await app.inject({ + method: 'GET', + path: '/generators/python', + query: { access_control: 'public' }, + }) + expect(body).toMatchInlineSnapshot(` + "from __future__ import annotations + + import datetime + import uuid + from typing import ( + Annotated, + Any, + List, + Literal, + NotRequired, + Optional, + TypeAlias, + TypedDict, + ) + + from pydantic import BaseModel, Field, Json + + PublicUserStatus: TypeAlias = Literal["ACTIVE", "INACTIVE"] + + PublicMemeStatus: TypeAlias = Literal["new", "old", "retired"] + + class PublicUsers(BaseModel): + decimal: Optional[float] = Field(alias="decimal") + id: int = Field(alias="id") + name: Optional[str] = Field(alias="name") + status: Optional[PublicUserStatus] = Field(alias="status") + user_uuid: Optional[uuid.UUID] = Field(alias="user_uuid") + + class PublicUsersInsert(TypedDict): + decimal: NotRequired[Annotated[float, Field(alias="decimal")]] + id: NotRequired[Annotated[int, Field(alias="id")]] + name: NotRequired[Annotated[str, Field(alias="name")]] + status: NotRequired[Annotated[PublicUserStatus, Field(alias="status")]] + user_uuid: NotRequired[Annotated[uuid.UUID, Field(alias="user_uuid")]] + + class PublicUsersUpdate(TypedDict): + decimal: NotRequired[Annotated[float, Field(alias="decimal")]] + id: NotRequired[Annotated[int, Field(alias="id")]] + name: NotRequired[Annotated[str, Field(alias="name")]] + status: NotRequired[Annotated[PublicUserStatus, Field(alias="status")]] + user_uuid: NotRequired[Annotated[uuid.UUID, Field(alias="user_uuid")]] + + class PublicTodos(BaseModel): + details: Optional[str] = Field(alias="details") + id: int = Field(alias="id") + user_id: int = Field(alias="user-id") + + class PublicTodosInsert(TypedDict): + details: NotRequired[Annotated[str, Field(alias="details")]] + id: NotRequired[Annotated[int, Field(alias="id")]] + user_id: Annotated[int, Field(alias="user-id")] + + class PublicTodosUpdate(TypedDict): + details: NotRequired[Annotated[str, Field(alias="details")]] + id: NotRequired[Annotated[int, Field(alias="id")]] + user_id: NotRequired[Annotated[int, Field(alias="user-id")]] + + class PublicUsersAudit(BaseModel): + created_at: Optional[datetime.datetime] = Field(alias="created_at") + id: int = Field(alias="id") + previous_value: Optional[Json[Any]] = Field(alias="previous_value") + user_id: Optional[int] = Field(alias="user_id") + + class PublicUsersAuditInsert(TypedDict): + created_at: NotRequired[Annotated[datetime.datetime, Field(alias="created_at")]] + id: NotRequired[Annotated[int, Field(alias="id")]] + previous_value: NotRequired[Annotated[Json[Any], Field(alias="previous_value")]] + user_id: NotRequired[Annotated[int, Field(alias="user_id")]] + + class PublicUsersAuditUpdate(TypedDict): + created_at: NotRequired[Annotated[datetime.datetime, Field(alias="created_at")]] + id: NotRequired[Annotated[int, Field(alias="id")]] + previous_value: NotRequired[Annotated[Json[Any], Field(alias="previous_value")]] + user_id: NotRequired[Annotated[int, Field(alias="user_id")]] + + class PublicUserDetails(BaseModel): + details: Optional[str] = Field(alias="details") + user_id: int = Field(alias="user_id") + + class PublicUserDetailsInsert(TypedDict): + details: NotRequired[Annotated[str, Field(alias="details")]] + user_id: Annotated[int, Field(alias="user_id")] + + class PublicUserDetailsUpdate(TypedDict): + details: NotRequired[Annotated[str, Field(alias="details")]] + user_id: NotRequired[Annotated[int, Field(alias="user_id")]] + + class PublicEmpty(BaseModel): + pass + + class PublicEmptyInsert(TypedDict): + pass + + class PublicEmptyUpdate(TypedDict): + pass + + class PublicTableWithOtherTablesRowType(BaseModel): + col1: Optional[PublicUserDetails] = Field(alias="col1") + col2: Optional[PublicAView] = Field(alias="col2") + + class PublicTableWithOtherTablesRowTypeInsert(TypedDict): + col1: NotRequired[Annotated[PublicUserDetails, Field(alias="col1")]] + col2: NotRequired[Annotated[PublicAView, Field(alias="col2")]] + + class PublicTableWithOtherTablesRowTypeUpdate(TypedDict): + col1: NotRequired[Annotated[PublicUserDetails, Field(alias="col1")]] + col2: NotRequired[Annotated[PublicAView, Field(alias="col2")]] + + class PublicTableWithPrimaryKeyOtherThanId(BaseModel): + name: Optional[str] = Field(alias="name") + other_id: int = Field(alias="other_id") + + class PublicTableWithPrimaryKeyOtherThanIdInsert(TypedDict): + name: NotRequired[Annotated[str, Field(alias="name")]] + other_id: NotRequired[Annotated[int, Field(alias="other_id")]] + + class PublicTableWithPrimaryKeyOtherThanIdUpdate(TypedDict): + name: NotRequired[Annotated[str, Field(alias="name")]] + other_id: NotRequired[Annotated[int, Field(alias="other_id")]] + + class PublicEvents(BaseModel): + created_at: datetime.datetime = Field(alias="created_at") + data: Optional[Json[Any]] = Field(alias="data") + event_type: Optional[str] = Field(alias="event_type") + id: int = Field(alias="id") + + class PublicEventsInsert(TypedDict): + created_at: NotRequired[Annotated[datetime.datetime, Field(alias="created_at")]] + data: NotRequired[Annotated[Json[Any], Field(alias="data")]] + event_type: NotRequired[Annotated[str, Field(alias="event_type")]] + id: NotRequired[Annotated[int, Field(alias="id")]] + + class PublicEventsUpdate(TypedDict): + created_at: NotRequired[Annotated[datetime.datetime, Field(alias="created_at")]] + data: NotRequired[Annotated[Json[Any], Field(alias="data")]] + event_type: NotRequired[Annotated[str, Field(alias="event_type")]] + id: NotRequired[Annotated[int, Field(alias="id")]] + + class PublicEvents2024(BaseModel): + created_at: datetime.datetime = Field(alias="created_at") + data: Optional[Json[Any]] = Field(alias="data") + event_type: Optional[str] = Field(alias="event_type") + id: int = Field(alias="id") + + class PublicEvents2024Insert(TypedDict): + created_at: NotRequired[Annotated[datetime.datetime, Field(alias="created_at")]] + data: NotRequired[Annotated[Json[Any], Field(alias="data")]] + event_type: NotRequired[Annotated[str, Field(alias="event_type")]] + id: Annotated[int, Field(alias="id")] + + class PublicEvents2024Update(TypedDict): + created_at: NotRequired[Annotated[datetime.datetime, Field(alias="created_at")]] + data: NotRequired[Annotated[Json[Any], Field(alias="data")]] + event_type: NotRequired[Annotated[str, Field(alias="event_type")]] + id: NotRequired[Annotated[int, Field(alias="id")]] + + class PublicEvents2025(BaseModel): + created_at: datetime.datetime = Field(alias="created_at") + data: Optional[Json[Any]] = Field(alias="data") + event_type: Optional[str] = Field(alias="event_type") + id: int = Field(alias="id") + + class PublicEvents2025Insert(TypedDict): + created_at: NotRequired[Annotated[datetime.datetime, Field(alias="created_at")]] + data: NotRequired[Annotated[Json[Any], Field(alias="data")]] + event_type: NotRequired[Annotated[str, Field(alias="event_type")]] + id: Annotated[int, Field(alias="id")] + + class PublicEvents2025Update(TypedDict): + created_at: NotRequired[Annotated[datetime.datetime, Field(alias="created_at")]] + data: NotRequired[Annotated[Json[Any], Field(alias="data")]] + event_type: NotRequired[Annotated[str, Field(alias="event_type")]] + id: NotRequired[Annotated[int, Field(alias="id")]] + + class PublicCategory(BaseModel): + id: int = Field(alias="id") + name: str = Field(alias="name") + + class PublicCategoryInsert(TypedDict): + id: NotRequired[Annotated[int, Field(alias="id")]] + name: Annotated[str, Field(alias="name")] + + class PublicCategoryUpdate(TypedDict): + id: NotRequired[Annotated[int, Field(alias="id")]] + name: NotRequired[Annotated[str, Field(alias="name")]] + + class PublicMemes(BaseModel): + category: Optional[int] = Field(alias="category") + created_at: datetime.datetime = Field(alias="created_at") + id: int = Field(alias="id") + metadata: Optional[Json[Any]] = Field(alias="metadata") + name: str = Field(alias="name") + status: Optional[PublicMemeStatus] = Field(alias="status") + + class PublicMemesInsert(TypedDict): + category: NotRequired[Annotated[int, Field(alias="category")]] + created_at: Annotated[datetime.datetime, Field(alias="created_at")] + id: NotRequired[Annotated[int, Field(alias="id")]] + metadata: NotRequired[Annotated[Json[Any], Field(alias="metadata")]] + name: Annotated[str, Field(alias="name")] + status: NotRequired[Annotated[PublicMemeStatus, Field(alias="status")]] + + class PublicMemesUpdate(TypedDict): + category: NotRequired[Annotated[int, Field(alias="category")]] + created_at: NotRequired[Annotated[datetime.datetime, Field(alias="created_at")]] + id: NotRequired[Annotated[int, Field(alias="id")]] + metadata: NotRequired[Annotated[Json[Any], Field(alias="metadata")]] + name: NotRequired[Annotated[str, Field(alias="name")]] + status: NotRequired[Annotated[PublicMemeStatus, Field(alias="status")]] + + class PublicAView(BaseModel): + id: Optional[int] = Field(alias="id") + + class PublicTodosView(BaseModel): + details: Optional[str] = Field(alias="details") + id: Optional[int] = Field(alias="id") + user_id: Optional[int] = Field(alias="user-id") + + class PublicUsersView(BaseModel): + decimal: Optional[float] = Field(alias="decimal") + id: Optional[int] = Field(alias="id") + name: Optional[str] = Field(alias="name") + status: Optional[PublicUserStatus] = Field(alias="status") + user_uuid: Optional[uuid.UUID] = Field(alias="user_uuid") + + class PublicUserTodosSummaryView(BaseModel): + todo_count: Optional[int] = Field(alias="todo_count") + todo_details: Optional[List[str]] = Field(alias="todo_details") + user_id: Optional[int] = Field(alias="user_id") + user_name: Optional[str] = Field(alias="user_name") + user_status: Optional[PublicUserStatus] = Field(alias="user_status") + + class PublicUsersViewWithMultipleRefsToUsers(BaseModel): + initial_id: Optional[int] = Field(alias="initial_id") + initial_name: Optional[str] = Field(alias="initial_name") + second_id: Optional[int] = Field(alias="second_id") + second_name: Optional[str] = Field(alias="second_name") + + class PublicTodosMatview(BaseModel): + details: Optional[str] = Field(alias="details") + id: Optional[int] = Field(alias="id") + user_id: Optional[int] = Field(alias="user-id") + + class PublicCompositeTypeWithArrayAttribute(BaseModel): + my_text_array: List[str] = Field(alias="my_text_array") + + class PublicCompositeTypeWithRecordAttribute(BaseModel): + todo: PublicTodos = Field(alias="todo")" + `) +}) + +test('typegen: python w/ excluded/included schemas', async () => { + // Create a test schema with some tables + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE SCHEMA IF NOT EXISTS test_schema; + CREATE TABLE IF NOT EXISTS test_schema.test_table ( + id serial PRIMARY KEY, + name text + ); + CREATE TABLE IF NOT EXISTS test_schema.another_table ( + id serial PRIMARY KEY, + value text + ); + `, + }, + }) + + try { + // Test excluded_schemas - should exclude test_schema + const { body: excludedBody } = await app.inject({ + method: 'GET', + path: '/generators/python', + query: { access_control: 'public', excluded_schemas: 'test_schema' }, + }) + expect(excludedBody).not.toContain('TestSchemaTestTable') + expect(excludedBody).not.toContain('TestSchemaAnotherTable') + expect(excludedBody).toContain('PublicUsers') + expect(excludedBody).toContain('PublicTodos') + + // Test included_schemas - should only include test_schema + const { body: includedBody } = await app.inject({ + method: 'GET', + path: '/generators/python', + query: { access_control: 'public', included_schemas: 'test_schema' }, + }) + expect(includedBody).toContain('TestSchemaTestTable') + expect(includedBody).toContain('TestSchemaAnotherTable') + expect(includedBody).not.toContain('PublicUsers') + expect(includedBody).not.toContain('PublicTodos') + + // Test multiple excluded schemas + const { body: multipleExcludedBody } = await app.inject({ + method: 'GET', + path: '/generators/python', + query: { access_control: 'public', excluded_schemas: 'test_schema,public' }, + }) + expect(multipleExcludedBody).not.toContain('TestSchemaTestTable') + expect(multipleExcludedBody).not.toContain('PublicUsers') + + // // Test multiple included schemas + const { body: multipleIncludedBody } = await app.inject({ + method: 'GET', + path: '/generators/python', + query: { access_control: 'public', included_schemas: 'public,test_schema' }, + }) + expect(multipleIncludedBody).toContain('TestSchemaTestTable') + expect(multipleIncludedBody).toContain('PublicUsers') + } finally { + // Clean up test schema + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + DROP SCHEMA IF EXISTS test_schema CASCADE; + `, + }, + }) + } +}) diff --git a/test/server/utils.ts b/test/server/utils.ts new file mode 100644 index 00000000..63e1e53d --- /dev/null +++ b/test/server/utils.ts @@ -0,0 +1,29 @@ +import { build as buildApp } from '../../src/server/app' + +export const app = buildApp() + +/** + * Normalizes UUIDs in test data to make snapshots resilient to UUID changes. + * Replaces all UUID strings with a consistent placeholder. + */ +export function normalizeUuids(data: unknown): unknown { + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i + + if (typeof data === 'string' && uuidRegex.test(data)) { + return '00000000-0000-0000-0000-000000000000' + } + + if (Array.isArray(data)) { + return data.map(normalizeUuids) + } + + if (data !== null && typeof data === 'object') { + const normalized: Record = {} + for (const [key, value] of Object.entries(data)) { + normalized[key] = normalizeUuids(value) + } + return normalized + } + + return data +} diff --git a/test/triggers.test.ts b/test/triggers.test.ts new file mode 100644 index 00000000..c537093c --- /dev/null +++ b/test/triggers.test.ts @@ -0,0 +1,186 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/triggers', () => { + test('should list triggers', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/triggers', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list triggers with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/triggers?include_system_schemas=true&limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should return 404 for non-existent trigger', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/triggers/non-existent-trigger', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) + + test('should create trigger, retrieve, update, delete', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/triggers', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_trigger1', + table: 'users_audit', + function_name: 'audit_action', + activation: 'AFTER', + events: ['UPDATE'], + }, + }) + expect(response.statusCode).toBe(200) + const responseData = response.json() + expect(responseData).toMatchObject({ + activation: 'AFTER', + condition: null, + enabled_mode: 'ORIGIN', + events: ['UPDATE'], + function_args: [], + function_name: 'audit_action', + function_schema: 'public', + id: expect.any(Number), + name: 'test_trigger1', + orientation: 'STATEMENT', + schema: 'public', + table: 'users_audit', + table_id: expect.any(Number), + }) + + const { id } = responseData + + const retrieveResponse = await app.inject({ + method: 'GET', + url: `/triggers/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(retrieveResponse.statusCode).toBe(200) + const retrieveData = retrieveResponse.json() + expect(retrieveData).toMatchObject({ + activation: 'AFTER', + condition: null, + enabled_mode: 'ORIGIN', + events: ['UPDATE'], + function_args: [], + function_name: 'audit_action', + function_schema: 'public', + id: expect.any(Number), + name: 'test_trigger1', + orientation: 'STATEMENT', + schema: 'public', + table: 'users_audit', + table_id: expect.any(Number), + }) + + const updateResponse = await app.inject({ + method: 'PATCH', + url: `/triggers/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_trigger1_updated', + enabled_mode: 'DISABLED', + }, + }) + expect(updateResponse.statusCode).toBe(200) + const updateData = updateResponse.json() + expect(updateData).toMatchObject({ + activation: 'AFTER', + condition: null, + enabled_mode: 'DISABLED', + events: ['UPDATE'], + function_args: [], + function_name: 'audit_action', + function_schema: 'public', + id: expect.any(Number), + name: 'test_trigger1_updated', + orientation: 'STATEMENT', + schema: 'public', + table: 'users_audit', + table_id: expect.any(Number), + }) + + const deleteResponse = await app.inject({ + method: 'DELETE', + url: `/triggers/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(deleteResponse.statusCode).toBe(200) + const deleteData = deleteResponse.json() + expect(deleteData).toMatchObject({ + activation: 'AFTER', + condition: null, + enabled_mode: 'DISABLED', + events: ['UPDATE'], + function_args: [], + function_name: 'audit_action', + function_schema: 'public', + id: expect.any(Number), + name: 'test_trigger1_updated', + orientation: 'STATEMENT', + schema: 'public', + table: 'users_audit', + table_id: expect.any(Number), + }) + }) + + test('should return 400 for invalid payload', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/triggers', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_trigger_invalid', + table: 'non_existent_table', + function_name: 'audit_action', + activation: 'AFTER', + events: ['UPDATE'], + }, + }) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "relation "public.non_existent_table" does not exist", + } + `) + }) +}) diff --git a/test/types.test.ts b/test/types.test.ts new file mode 100644 index 00000000..df2af697 --- /dev/null +++ b/test/types.test.ts @@ -0,0 +1,46 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/types', () => { + test('should list types', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/types', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list types with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/types?include_system_schemas=true&limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should return 404 for non-existent type', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/types/non-existent-type', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) +}) diff --git a/test/utils.test.ts b/test/utils.test.ts new file mode 100644 index 00000000..3d70b1a5 --- /dev/null +++ b/test/utils.test.ts @@ -0,0 +1,105 @@ +import { expect, test, describe } from 'vitest' +import { FastifyRequest } from 'fastify' +import { + extractRequestForLogging, + createConnectionConfig, + translateErrorToResponseCode, +} from '../src/server/utils.js' + +describe('server/utils', () => { + describe('extractRequestForLogging', () => { + test('should extract request information for logging', () => { + const mockRequest = { + method: 'GET', + url: '/test', + headers: { + 'user-agent': 'test-agent', + 'x-supabase-info': 'test-info', + }, + query: { param: 'value' }, + } as FastifyRequest + + const result = extractRequestForLogging(mockRequest) + expect(result).toHaveProperty('method') + expect(result).toHaveProperty('url') + expect(result).toHaveProperty('pg') + expect(result).toHaveProperty('opt') + }) + + test('should handle request with minimal properties', () => { + const mockRequest = { + method: 'POST', + url: '/api/test', + headers: {}, + } as FastifyRequest + + const result = extractRequestForLogging(mockRequest) + expect(result.method).toBe('POST') + expect(result.url).toBe('/api/test') + expect(result.pg).toBe('unknown') + }) + }) + + describe('createConnectionConfig', () => { + test('should create connection config from request headers', () => { + const mockRequest = { + headers: { + pg: 'postgresql://user:pass@localhost:5432/db', + 'x-pg-application-name': 'test-app', + }, + } as FastifyRequest + + const result = createConnectionConfig(mockRequest) + expect(result).toHaveProperty('connectionString') + expect(result).toHaveProperty('application_name') + expect(result.connectionString).toBe('postgresql://user:pass@localhost:5432/db') + expect(result.application_name).toBe('test-app') + }) + + test('should handle request without application name', () => { + const mockRequest = { + headers: { + pg: 'postgresql://user:pass@localhost:5432/db', + }, + } as FastifyRequest + + const result = createConnectionConfig(mockRequest) + expect(result).toHaveProperty('connectionString') + expect(result.connectionString).toBe('postgresql://user:pass@localhost:5432/db') + // application_name should have default value if not provided + expect(result.application_name).toBe('postgres-meta 0.0.0-automated') + }) + }) + + describe('translateErrorToResponseCode', () => { + test('should return 504 for connection timeout errors', () => { + const error = { message: 'Connection terminated due to connection timeout' } + const result = translateErrorToResponseCode(error) + expect(result).toBe(504) + }) + + test('should return 503 for too many clients errors', () => { + const error = { message: 'sorry, too many clients already' } + const result = translateErrorToResponseCode(error) + expect(result).toBe(503) + }) + + test('should return 408 for query timeout errors', () => { + const error = { message: 'Query read timeout' } + const result = translateErrorToResponseCode(error) + expect(result).toBe(408) + }) + + test('should return default 400 for other errors', () => { + const error = { message: 'database connection failed' } + const result = translateErrorToResponseCode(error) + expect(result).toBe(400) + }) + + test('should return custom default for other errors', () => { + const error = { message: 'some other error' } + const result = translateErrorToResponseCode(error, 500) + expect(result).toBe(500) + }) + }) +}) diff --git a/test/views.test.ts b/test/views.test.ts new file mode 100644 index 00000000..d713e919 --- /dev/null +++ b/test/views.test.ts @@ -0,0 +1,51 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/views', () => { + test('should list views', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/views', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list views with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/views?include_system_schemas=true&limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should return 404 for non-existent view', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/views/1', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "Cannot find a view with ID 1", + } + `) + await app.close() + }) +}) diff --git a/tsconfig.module.json b/tsconfig.jest.json similarity index 52% rename from tsconfig.module.json rename to tsconfig.jest.json index d424e626..f4c451fd 100644 --- a/tsconfig.module.json +++ b/tsconfig.jest.json @@ -1,7 +1,6 @@ { "extends": "./tsconfig", "compilerOptions": { - "module": "ES2015", - "outDir": "dist/module" + "rootDir": "." } } diff --git a/tsconfig.json b/tsconfig.json index b39fac61..24a8d4ca 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,21 @@ { - "include": ["src/lib"], - "exclude": ["src/lib/connection*.ts"], + "include": ["src"], "compilerOptions": { + "incremental": true, "declaration": true, "declarationMap": true, - "module": "CommonJS", - "outDir": "dist/main", - "rootDir": "src/lib", + "module": "NodeNext", + "outDir": "dist", + "rootDir": "src", "sourceMap": true, - "target": "ES2015", + "target": "esnext", "strict": true, "esModuleInterop": true, - "moduleResolution": "Node", + "moduleResolution": "NodeNext", "resolveJsonModule": true, - + "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "stripInternal": true } diff --git a/tsconfig.server.json b/tsconfig.server.json deleted file mode 100644 index 8d45c861..00000000 --- a/tsconfig.server.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "declaration": true, - "declarationMap": true, - "module": "CommonJS", - "outDir": "bin", - "sourceMap": true, - "target": "ES2015", - - "strict": true, - - "esModuleInterop": true, - "moduleResolution": "Node", - "resolveJsonModule": true, - - "forceConsistentCasingInFileNames": true, - "stripInternal": true - } -} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..da50a76b --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,11 @@ +/// +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + coverage: { reporter: ['lcov'] }, + maxConcurrency: 1, + // https://github.com/vitest-dev/vitest/issues/317#issuecomment-1542319622 + pool: 'forks', + }, +})