diff --git a/.github/workflows/annualLicenseUpdate.yaml b/.github/workflows/annualLicenseUpdate.yaml new file mode 100644 index 00000000..9b9aaf5c --- /dev/null +++ b/.github/workflows/annualLicenseUpdate.yaml @@ -0,0 +1,34 @@ +name: 📝 Annual License Update + +on: + # This workflow runs on Jan 1st every year + schedule: + - cron: "0 0 1 1 *" + +jobs: + update-license: + runs-on: ubuntu-latest + permissions: + contents: write # to checkout the code and update the LICENSE file + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} + + - name: Update Copyright Date Range in LICENSE + run: | + function log_error() { echo '🚨LICENSE UPDATE FAILED🚨'; exit 1; } + + year=$(date +%Y) + + sed -i -E \ + "s/(Copyright © [0-9]{4})-[0-9]{4}(.*)/\1-$year\2/" ./LICENSE || log_error + + git config --global user.email "${{ github.actor }}@users.noreply.github.com" + git config --global user.name "${{ github.actor }}" + git add ./LICENSE + + git commit -m \ + "chore(license): update copyright date range to include $year" || log_error + + echo 'License Copyright date range successfully updated.' diff --git a/.github/workflows/cicd.yaml b/.github/workflows/cicd.yaml new file mode 100644 index 00000000..9b230b53 --- /dev/null +++ b/.github/workflows/cicd.yaml @@ -0,0 +1,33 @@ +name: 🚀 CI/CD Workflow + +on: + pull_request: + types: [opened, reopened, synchronize] # default PR types + branches: [main, next] + paths: ["src/**/*", "package*.json"] + push: + branches: [main, next] + paths: ["src/**/*", "package*.json"] + # This workflow can be manually triggered + workflow_dispatch: + +jobs: + test: + name: 🧪 Test + uses: ./.github/workflows/test.yaml + secrets: inherit + permissions: + contents: write # to checkout the code and merge bot-PRs + pull-requests: write # to add coverage reports to the PR + statuses: write # to update commit status + + release: + name: 📦 Release + needs: test # run job if event=push and tests passed + if: github.event_name == 'push' && needs.test.outputs.success == 'true' + uses: ./.github/workflows/release.yaml + secrets: inherit + permissions: + attestations: write # to generate artifact attestations for dist assets + contents: write # to create a release + issues: write # to be able to comment on released issues diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 0475ed93..0c75aed5 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -1,23 +1,24 @@ -name: Deploy Workflow +name: 🚀 Deploy Workflow on: + # This workflow runs for both releases and pre-releases release: { types: [published] } workflow_dispatch: jobs: deploy: - name: Deploy to ECS + name: 🚀 Deploy to ECS runs-on: ubuntu-latest - - # Permissions required to use aws-actions/configure-aws-credentials: - permissions: { id-token: write, contents: read } - + permissions: + contents: read # to checkout the code + id-token: write # to assume the OIDC role (configure-aws-credentials) steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - - uses: aws-actions/configure-aws-credentials@v4 + - name: Configure AWS Credentials for ECR + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.ECR_OIDC_GITHUB_ROLE_ARN }} aws-region: ${{ secrets.ECR_REPO_REGION }} @@ -36,26 +37,27 @@ jobs: # 3. env tag (if release: "prod", anything else: "staging") # 4. relative pointer (if release: "latest", anything else: "next") run: | - IMAGE_TAGS=( "${{ github.sha }}" ) + image_tags=( "${{ github.sha }}" ) if [ "${{ github.event_name }}" == 'release' ]; then - IMAGE_TAGS+=( "${{ github.event.release.tag_name }}" ) + image_tags+=( "${{ github.event.release.tag_name }}" ) fi if [[ "${{ github.event_name }}" == 'release' && "${{ github.event.release.prerelease }}" == 'false' ]]; then - IMAGE_TAGS+=( prod latest ) + image_tags+=( prod latest ) else - IMAGE_TAGS+=( staging next ) + image_tags+=( staging next ) fi - IMAGE_REPO="${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_PRIVATE_REPO }}" - IMAGE_TAGS=("${IMAGE_TAGS[@]/#/$IMAGE_REPO:}") + image_repo="${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_PRIVATE_REPO }}" + image_tags=("${image_tags[@]/#/$image_repo:}") - docker build ${IMAGE_TAGS[@]/#/--tag } . + docker build ${image_tags[@]/#/--tag } . - for tag in "${IMAGE_TAGS[@]}"; do docker push "$tag"; done + for tag in "${image_tags[@]}"; do docker push "$tag"; done - - uses: aws-actions/configure-aws-credentials@v4 + - name: Configure AWS Credentials for ECS + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.ECS_OIDC_GITHUB_ROLE_ARN }} aws-region: ${{ secrets.ECS_CLUSTER_REGION }} @@ -63,22 +65,22 @@ jobs: - name: Update ECS Task Definition & Service run: | if [[ "${{ github.event_name }}" == 'release' && "${{ github.event.release.prerelease }}" == 'false' ]]; then - TASK_DEF_NAME=${{ secrets.ECS_API_TASK_DEF_PROD }} - SERVICE_NAME=${{ secrets.ECS_API_SERVICE_NAME_PROD }} - CLUSTER_NAME=${{ secrets.ECS_CLUSTER_NAME_PROD }} + task_def_name=${{ secrets.ECS_API_TASK_DEF_PROD }} + service_name=${{ secrets.ECS_API_SERVICE_NAME_PROD }} + cluster_name=${{ secrets.ECS_CLUSTER_NAME_PROD }} else - TASK_DEF_NAME=${{ secrets.ECS_API_TASK_DEF_STAGING }} - SERVICE_NAME=${{ secrets.ECS_API_SERVICE_NAME_STAGING }} - CLUSTER_NAME=${{ secrets.ECS_CLUSTER_NAME_STAGING }} + task_def_name=${{ secrets.ECS_API_TASK_DEF_STAGING }} + service_name=${{ secrets.ECS_API_SERVICE_NAME_STAGING }} + cluster_name=${{ secrets.ECS_CLUSTER_NAME_STAGING }} fi - IMAGE_REPO="${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_PRIVATE_REPO }}" + image_repo="${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_PRIVATE_REPO }}" - UPDATED_TASK_DEF_JSON=$( + updated_task_def_json=$( aws ecs describe-task-definition \ - --task-definition $TASK_DEF_NAME \ + --task-definition $task_def_name \ --output json | \ - jq --arg NEW_IMAGE "$IMAGE_REPO:${{ github.sha }}" \ + jq --arg NEW_IMAGE "$image_repo:${{ github.sha }}" \ '.taskDefinition | .containerDefinitions[0].image = $NEW_IMAGE | del(.taskDefinitionArn) | @@ -91,12 +93,12 @@ jobs: ) aws ecs register-task-definition \ - --cli-input-json "$UPDATED_TASK_DEF_JSON" \ + --cli-input-json "$updated_task_def_json" \ 1>/dev/null aws ecs update-service \ - --cluster $CLUSTER_NAME \ - --service $SERVICE_NAME \ - --task-definition $TASK_DEF_NAME \ + --cluster $cluster_name \ + --service $service_name \ + --task-definition $task_def_name \ --force-new-deployment \ 1>/dev/null diff --git a/.github/workflows/publishApiSchema.yaml b/.github/workflows/publishApiSchema.yaml new file mode 100644 index 00000000..ff6668af --- /dev/null +++ b/.github/workflows/publishApiSchema.yaml @@ -0,0 +1,62 @@ +name: Publish API Schema + +on: + # This workflow runs for both releases and pre-releases + release: { types: [published] } + # This workflow can be manually triggered + workflow_dispatch: + +jobs: + publish-open-api-schema: + name: Publish OpenAPI Schema + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - name: Setup Publication Tools + run: npm ci --include=dev + + - name: Run Publication Script + env: + SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }} + run: | + # If prerelease is false, use --setdefault flag to update the default version + should_set_default=$( + [ ${{ github.event.release.prerelease }} == 'false' ] && + echo '--setdefault' || + echo '' + ) + + scripts/cicd.publish-schema-open-api.sh \ + --version=${{ github.event.release.tag_name }} \ + $should_set_default + + publish-graphql-schema: + name: Publish GraphQL Schema + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - name: Setup Publication Tools + run: npm ci --include=dev + + - name: Run Publication Script + env: + APOLLO_KEY: ${{ secrets.APOLLO_STUDIO_GRAPH_API_KEY }} + run: | + # If prerelease is false, then variant is prod, else variant is staging + graph_variant=$( + [ ${{ github.event.release.prerelease }} == 'false' ] && + echo prod || + echo staging + ) + + scripts/cicd.publish-schema-graphql.sh --variant=$graph_variant diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 994e9e52..ec450675 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,16 +1,43 @@ -name: Release Workflow - -# This workflow is called by the Test workflow when all tests pass. +name: 📦 Release Workflow on: - workflow_dispatch: + # This workflow is called by the CI/CD Workflow (see ./cicd.yaml) workflow_call: secrets: SEMANTIC_RELEASE_TOKEN: { required: true } + # This workflow can be manually triggered + workflow_dispatch: jobs: release: - name: Release - uses: Nerdware-LLC/reusable-action-workflows/.github/workflows/release.yaml@main - secrets: - SEMANTIC_RELEASE_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} + name: 📦 Release + runs-on: ubuntu-latest + permissions: + attestations: write # to generate artifact attestations for dist assets + contents: write # to create a release + issues: write # to be able to comment on released issues + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - name: Install Dependencies + run: npm ci --include=dev + + - name: Build Dist Assets + run: npm run build + + - name: Run Semantic-Release + id: semantic-release + uses: cycjimmy/semantic-release-action@v4 + with: + extra_plugins: | + @aensley/semantic-release-openapi@1.1.8 + @semantic-release/changelog@6.0.3 + @semantic-release/git@10.0.1 + env: + GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d59900ff..a38132cd 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,22 +1,25 @@ -name: Test Workflow +name: 🧪 Test Workflow on: + # This workflow is called by the CI/CD Workflow (see ./cicd.yaml) + workflow_call: + secrets: + CODECOV_TOKEN: { required: true } + outputs: + success: + description: "Whether the tests passed" + value: ${{ jobs.run-tests.outputs.success }} + # This workflow can be manually triggered workflow_dispatch: - pull_request: - types: [opened, reopened, synchronize] # default PR types - branches: [main, next] - paths: ["src/**/*", "package*.json"] - push: - branches: [main, next] - paths: ["src/**/*", "package*.json"] jobs: - test: - name: Run Tests + run-tests: + name: 🧪 Run Tests runs-on: ubuntu-latest + outputs: + success: ${{ steps.run-tests.outputs.success }} permissions: - contents: read # to checkout the code - pull-requests: write # to add coverage reports to the PR + contents: read # to checkout the code steps: - uses: actions/checkout@v4 @@ -24,52 +27,85 @@ jobs: with: node-version-file: ".nvmrc" - - run: npm ci --include=dev + - name: Install Dependencies + run: npm ci --include=dev - - name: Lint + - name: Run Linters run: npm run lint - name: Run Tests - if: success() id: run-tests - run: npm run test:ci - - - name: Update GitHub Commit Status - if: ${{ !cancelled() }} run: | - if [ ${{ steps.run-tests.outcome }} == 'success' ]; then - commit_status_state='success' - description='Tests passed' - else - commit_status_state='failure' - description='Tests failed' - fi + npm run test:ci + echo "success=$( [ $? == 0 ] && echo true || echo false )" >> "$GITHUB_OUTPUT" - curl --request POST \ - --url https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }} \ - --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ - --header 'content-type: application/json' \ - --data "{ - \"context\": \"tests\", - \"state\": \"$commit_status_state\", - \"description\": \"$description\", - \"target_url\": \"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\" - }" + - name: Upload Test Coverage Reports + uses: actions/upload-artifact@v4 + with: + name: test-coverage-reports + path: ./coverage - - name: Update CodeCov - if: success() - uses: codecov/codecov-action@v4 + update-codecov: + name: ☂️ Update CodeCov + runs-on: ubuntu-latest + needs: run-tests # run job if tests passed + if: needs.run-tests.outputs.success == 'true' + steps: + - uses: actions/download-artifact@v4 + with: { name: test-coverage-reports } + - uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} - - name: Update PR with Coverage Reports - if: github.event_name == 'pull_request' + update-pull-request: + name: 📝 Update PR + runs-on: ubuntu-latest + needs: run-tests # run job if event=PR and tests passed + if: github.event_name == 'pull_request' && needs.run-tests.outputs.success == 'true' + permissions: + contents: write # to auto-merge dependabot PRs + pull-requests: write # to update the PR + steps: + - uses: actions/checkout@v4 + - name: Download Test Coverage Reports + uses: actions/download-artifact@v4 + with: { name: test-coverage-reports, path: ./coverage } + - name: Add Vitest Test Coverage Report to PR uses: davelosert/vitest-coverage-report-action@v2 + - name: "(dependabot🤖) Approve & Auto-Merge PR" + if: github.actor == 'dependabot[bot]' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + gh pr review --approve "$PR_URL" + gh pr merge --auto --merge "$PR_URL" - release: - name: Release if Tests Passed (push/dispatch only) - needs: test - if: github.event_name != 'pull_request' && needs.test.result == 'success' - uses: ./.github/workflows/release.yaml - secrets: - SEMANTIC_RELEASE_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} + update-github-commit-status: + name: 📡 Update GitHub Commit Status + runs-on: ubuntu-latest + needs: run-tests # always run job unless the workflow was cancelled + if: ${{ !cancelled() }} + permissions: + statuses: write # to update commit status + steps: + - run: | + if [ ${{ needs.run-tests.outputs.success }} == 'true' ]; then + commit_status_state='success' + description='Tests passed' + else + commit_status_state='failure' + description='Tests failed' + fi + + curl --request POST \ + --url https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }} \ + --header 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ + --header 'Accept: application/vnd.github+json' \ + --header 'X-GitHub-Api-Version: 2022-11-28' \ + --data "{ + \"context\": \"tests\", + \"state\": \"$commit_status_state\", + \"description\": \"$description\", + \"target_url\": \"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\" + }" diff --git a/.releaserc.yaml b/.releaserc.yaml index 3c23da52..8856c1f2 100644 --- a/.releaserc.yaml +++ b/.releaserc.yaml @@ -28,6 +28,9 @@ plugins: - - "@semantic-release/npm" - npmPublish: false + - - "@aensley/semantic-release-openapi" + - apiSpecFiles: ["schemas/OpenAPI/open-api.yaml"] + - - "@semantic-release/github" - addReleases: bottom assignees: trevor-anderson @@ -58,3 +61,4 @@ plugins: - "package.json" - "package-lock.json" - "npm-shrinkwrap.json" + - "schemas/OpenAPI/open-api.yaml" diff --git a/.swcrc b/.swcrc index 560b2076..43e70b79 100644 --- a/.swcrc +++ b/.swcrc @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/swcrc", - "exclude": ["src\\/loader.js$", ".*\\/(tests|__mocks__)\\/.*", ".*\\.test.[tj]s$"], + "exclude": [".*\\/(tests|__mocks__)\\/.*", ".*\\.test.[tj]s$"], "jsc": { "target": "esnext", @@ -17,17 +17,7 @@ "mangle": true, "compress": { "ecma": "2022", - "module": true, - "top_retain": [ - "UUID_V1_REGEX_STR", - "CONTACT_SK_PREFIX_STR", - "INVOICE_SK_PREFIX_STR", - "STRIPE_CONNECT_ACCOUNT_SK_PREFIX_STR", - "USER_SUB_SK_PREFIX_STR", - "USER_SK_PREFIX_STR", - "WORK_ORDER_SK_PREFIX_STR", - "USER_ID_PREFIX_STR" - ] + "module": true } } }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fb65467..e5d1aea4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,58 @@ All notable changes to this project will be documented in this file. --- + +# [2.2.0-next.1](https://github.com/Nerdware-LLC/fixit-api/compare/v2.1.2...v2.2.0-next.1) (2024-08-06) + + +### Bug Fixes + +* add check for nullish req.body ([d770924](https://github.com/Nerdware-LLC/fixit-api/commit/d770924c62b43776d7bfad76d0b7e9a61d115ab1)) +* add check for undefined req.body ([36facb4](https://github.com/Nerdware-LLC/fixit-api/commit/36facb471b8511b560125d496db27a7aa351e491)) +* add default str for nullish paymentIntentID value ([0ff42ea](https://github.com/Nerdware-LLC/fixit-api/commit/0ff42ea85326fc210fa9f056fe00e9f9a1628741)) +* add explicit 'unknown' typing to err params in catch ([54ce108](https://github.com/Nerdware-LLC/fixit-api/commit/54ce10855cfc21d1737ef27c7f84c9d7a10c2bb1)) +* add fallback str in case req.ip is nullish ([0b819f5](https://github.com/Nerdware-LLC/fixit-api/commit/0b819f51f4fcf0947708fe0e450cabcab23f9bbf)) +* add handle lookup to ensure uniqueness ([dd8536a](https://github.com/Nerdware-LLC/fixit-api/commit/dd8536a8c8c9bb704b7077c8450297a1e7b5f093)) +* add opt chains for nullish lookups ([063adbe](https://github.com/Nerdware-LLC/fixit-api/commit/063adbec588ba244068e93377526bda4e5971295)) +* add status400ForVariableCoercionErrors:true per apollo recommendation ([6cb5a21](https://github.com/Nerdware-LLC/fixit-api/commit/6cb5a21a21168363074c124c369ca40b430228b0)) +* correct imports ([97e5e07](https://github.com/Nerdware-LLC/fixit-api/commit/97e5e07433a9d99ed8c66abd33bf40c4a65f7131)) +* correct the 200 response to 200AuthTokenAndPreFetchedUserItems ([36cc8d3](https://github.com/Nerdware-LLC/fixit-api/commit/36cc8d3102cf9a019c8cb9964d16db9f551464ee)) +* **mock:** promisify return type of InvokeCommand ([cf663a9](https://github.com/Nerdware-LLC/fixit-api/commit/cf663a94293eb81644b66fdce88bb8654f7dd216)) +* **mock:** promisify return type of SendMessagesCommand ([fc4eba4](https://github.com/Nerdware-LLC/fixit-api/commit/fc4eba4416df35ac5f52af2ea21ad767ac15024c)) +* replace sanitizeID w correct regex impl ([f9145b4](https://github.com/Nerdware-LLC/fixit-api/commit/f9145b427ca11c80c8684f800ec7dd97030c0271)) +* rm export of deleted express types file ([b04e5e6](https://github.com/Nerdware-LLC/fixit-api/commit/b04e5e6c4981f407a5ef7f6b214abf5823168b23)) +* rm unnecessary as-cast from obj.__typename ([d681187](https://github.com/Nerdware-LLC/fixit-api/commit/d681187288e603e4e49deaebfc9585ec7664ee4c)) +* update ENV value paths ([115f51b](https://github.com/Nerdware-LLC/fixit-api/commit/115f51bf310ba6690a20e941c0b89af41d791aca)) +* update GQL codegen's types w Context typing and docstring descriptions ([026a1ef](https://github.com/Nerdware-LLC/fixit-api/commit/026a1ef07660c1cd2b571eb0965fa5365c89194d)) +* update regex used to skip webhooks body parsing ([df8aadf](https://github.com/Nerdware-LLC/fixit-api/commit/df8aadfbbe1c31efbe8e59cdbe8a52540e2e0e8a)) +* update req.body type to be possibly undefined ([f200471](https://github.com/Nerdware-LLC/fixit-api/commit/f200471303af190fedc6cda3b9ce4c8f0d5424c1)) + + +### Features + +* add auth method verifyUserIsAuthorizedToAccessPaidContent ([bd59a3d](https://github.com/Nerdware-LLC/fixit-api/commit/bd59a3dbd85b77261fe79a159e28a6890d1e614e)) +* add ContactService ([3d0e228](https://github.com/Nerdware-LLC/fixit-api/commit/3d0e2287408a0baf7e0b3b06e71efaa9fbfa8802)) +* add emit 'CheckoutCompleted' event ([c79c011](https://github.com/Nerdware-LLC/fixit-api/commit/c79c0114cbac47a65bbb876e80a94b066db98576)) +* add env vars for v5 UUID namespace and others ([d2cfd4d](https://github.com/Nerdware-LLC/fixit-api/commit/d2cfd4da13b1dc81976884c2e0db236b7c23697e)) +* add error-handling to `sendMessages` ([8001abe](https://github.com/Nerdware-LLC/fixit-api/commit/8001abe0ccff522c4a5af7d0cb9872f268b2540c)) +* add event 'CheckoutCompleted' and handler `sendConfirmationEmail` ([e541826](https://github.com/Nerdware-LLC/fixit-api/commit/e5418266e07cdb70d4e656ac2c98b5e65c13140f)) +* add httpServer wrapper ([1c52013](https://github.com/Nerdware-LLC/fixit-api/commit/1c52013670ce738e5d22286f6d0d484c19948e18)) +* add InvoiceService ([5712d61](https://github.com/Nerdware-LLC/fixit-api/commit/5712d61315d95a7cec39741cbde9713747e1b0a1)) +* add method 'getUserByHandleOrID' ([a14f92a](https://github.com/Nerdware-LLC/fixit-api/commit/a14f92a586b2de12b56d7bbed455d058e75de988)) +* add pinpoint sdk, lib wrapper, and SendMessages invocations ([d7ba6a2](https://github.com/Nerdware-LLC/fixit-api/commit/d7ba6a21d1f5de6f152801e4c39c3091add164e6)) +* add reset-password functionality ([b7b7c41](https://github.com/Nerdware-LLC/fixit-api/commit/b7b7c41ebc1c597094e5f07bd7208c640a8a646a)) +* add type BaseEventHandler for static EVENT_HANDLERS ([7d210e8](https://github.com/Nerdware-LLC/fixit-api/commit/7d210e8f35a359b3f3244e568dd806e9538ea73a)) +* add UserService method 'getUserByHandleOrID' ([7ffb750](https://github.com/Nerdware-LLC/fixit-api/commit/7ffb7507cbeed2179ec33b176b81a4b9b40e526b)) +* add WorkOrderService ([58422cf](https://github.com/Nerdware-LLC/fixit-api/commit/58422cfab997358d16a6c5aafe080daad04e3ad6)) +* add zod schema for gql input types ([cccc837](https://github.com/Nerdware-LLC/fixit-api/commit/cccc83742e9cc23dee63283b7d57a928175cdff1)) +* add zod-related util types ([4e72dc1](https://github.com/Nerdware-LLC/fixit-api/commit/4e72dc16fdb85116de075c498a0022259681c782)) +* migrate FixitUser interface to PublicUserFields ([daa0393](https://github.com/Nerdware-LLC/fixit-api/commit/daa039384737ae3dbd37c24023be9233ad5ab47c)) +* rm AuthToken gql typeDef ([e2fa376](https://github.com/Nerdware-LLC/fixit-api/commit/e2fa3764160bb15cfbce29ae9dddf7aa9e6f6f2b)) +* rm GQL-specific HttpError classes ([9a3a683](https://github.com/Nerdware-LLC/fixit-api/commit/9a3a6836f663ecd27fb9f0d9a5e9ef37d59b2dd1)) +* rm old method ([677af6f](https://github.com/Nerdware-LLC/fixit-api/commit/677af6f9a82e81a0f7d04c0a634d130c9af60c05)) +* rm res.locals types ([686bef2](https://github.com/Nerdware-LLC/fixit-api/commit/686bef2c81459d9767ec512aef0ce60a86d4c0c4)) +* rm unused sanitizeStripeID fn ([0e6c2e6](https://github.com/Nerdware-LLC/fixit-api/commit/0e6c2e6d1a1cc8a21391e22e76180ed511306b9c)) + ## [2.1.4](https://github.com/Nerdware-LLC/fixit-api/compare/v2.1.3...v2.1.4) (2024-04-04) ## [2.1.3](https://github.com/Nerdware-LLC/fixit-api/compare/v2.1.2...v2.1.3) (2024-04-03) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 75c1a55b..03e93a2c 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -17,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or +- The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities @@ -60,7 +60,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -trevor@nerdware.cloud. +[trevor@nerdware.cloud](mailto:trevor@nerdware.cloud). All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the @@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an +standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within @@ -116,7 +116,7 @@ the community. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. +. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). @@ -124,5 +124,5 @@ enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. +. Translations are available at +. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e1c95c54..590b7a1d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,20 +23,31 @@ This project uses [GitHub Flow](https://guides.github.com/introduction/flow/), s ## Project Layout -- [**`.github/`**](/.github)                           GitHub Actions workflows and other GitHub-related files -- [**`docs/`**](/docs)                                REST API OpenAPI schema files -- [**`fixit@current.graphql`**](/fixit%40current.graphql)     GraphQL API schema -- [**`src/`**](/src)                                  Source code files - - [**`src/events/`**](/src/events)               Event emitter and handlers - - [**`src/graphql/`**](/src/graphql)             GraphQL typedefs and resolvers - - [**`src/lib/`**](/src/lib)                    Third-party clients and internal cache - - [**`src/middleware/`**](/src/middleware)        Middleware functions used by routers/ - - [**`src/models/`**](/src/models)               Data-defining classes which implement DB CRUD operations - - [**`src/routers/`**](/src/routers)             Express routers - - [**`src/server/`**](/src/server)               Server init logic and process handlers - - [**`src/tests/`**](/src/tests/README.md)                 End-to-end tests and the Vitest setup file - - [**`src/types/`**](/src/types)                 Global type definitions and codegen'd types - - [**`src/utils/`**](/src/utils)                 Utility functions +| Dir | Purpose | +| :---------------------------------------------------------- | :--------------------------------------------------------- | +| [**`.github/`**](/.github) | GitHub Actions workflows and other GitHub-related files | +| [**`schemas/`**](/schemas) | API schemas (OpenAPI) | +| [**`src/`**](/src) | Source code | +|   [**`src/index.ts`**](/src/index.ts) | The server startup entrypoint | +|   [**`src/httpServer.ts`**](/src/httpServer,ts) | The NodeJS [http.Server][node-http-ref] instance | +|   [**`src/apolloServer.ts`**](/src/apolloServer.ts) | The [ApolloServer][apollo-server-api-ref] instance | +|   [**`src/expressApp.ts`**](/src/expressApp.ts) | The [Express][express-api-ref] app instance | +|   [**`src/controllers/`**](/src/controllers/README.md) | API request/response handlers | +|   [**`src/events/`**](/src/events) | NodeJS event emitter and handlers | +|   [**`src/graphql/`**](/src/graphql) | GraphQL typedefs and resolvers | +|   [**`src/lib/`**](/src/lib) | Third-party clients and internal cache | +|   [**`src/middleware/`**](/src/middleware) | Middleware functions | +|   [**`src/models/`**](/src/models/README.md) | Data-defining classes which encapsulate db CRUD operations | +|   [**`src/routes/`**](/src/routes/README.md) | Express routers for REST path-based routes | +|   [**`src/server/`**](/src/server) | Server init logic and process event handlers | +|   [**`src/services/`**](/src/services/README.md) | Business-logic handlers | +|   [**`src/tests/`**](/src/tests/README.md) | End-to-end tests and the Vitest setup file | +|   [**`src/types/`**](/src/types) | Global type definitions and codegen'd types | +|   [**`src/utils/`**](/src/utils) | Utility functions | + +[node-http-ref]: https://nodejs.org/docs/latest-v20.x/api/http.html +[apollo-server-api-ref]: https://www.apollographql.com/docs/apollo-server/api/apollo-server/ +[express-api-ref]: https://expressjs.com/en/4x/api.html ## Commit Messages @@ -58,7 +69,7 @@ This project uses [Semantic Release](https://github.com/semantic-release/semanti > > The robot minions work hard to manage these - **_please don't upset them_** 🤖 -Once tests are passing on your pull request, and it has been approved by a maintainer, it will be merged into the `next` branch, which will trigger a versioned pre-release. After final review and approval of the pre-release build, a maintainer will merge `next` into `main`, which will trigger a release build of the package to be published to [npm](https://www.npmjs.com/package/ddb-single-table). +Once tests are passing on your pull request, and it has been approved by a maintainer, it will be merged into the `next` branch, which will trigger a versioned pre-release. After final review and approval of the pre-release build, a maintainer will merge `next` into `main`, which will trigger a release build. ## Code of Conduct diff --git a/LICENSE b/LICENSE index 089979bd..daa357dc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright © Nerdware, LLC + * Copyright © 2022-2024 Nerdware, LLC * Trevor Anderson, * Managing Director * diff --git a/README.md b/README.md index 59b61773..123957ae 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@
-[](https://www.youtube.com/@nerdware-io) +[Repo banner](https://www.youtube.com/@nerdware-io) Fixit is a SaaS product that provides users with powerful tools for managing maintenance and repair workflows, including in-app payments powered by [Stripe](https://stripe.com/). This repo is home to the dual-protocol Fixit API – built on NodeJS, ExpressJS, and Apollo GraphQL. Author: [Trevor Anderson](https://github.com/trevor-anderson), Solopreneur & Founder of [Nerdware](https://github.com/Nerdware-LLC) -[](https://stripe.com/ "Check out Stripe") +[Powered by Stripe](https://stripe.com/ "Check out Stripe")   [![Test Workflow][gh-test-badge]](.github/workflows/test.yaml "View Test Workflow")   @@ -46,6 +46,7 @@ Author: [Trevor Anderson](https://github.com/trevor-anderson), Solopreneur & Fou - [REST Endpoints Diagram](#rest-endpoints-diagram) - [GraphQL Schema](#graphql-schema) - [🔐 User Authentication](#-user-authentication) +- [💎 Premium SaaS Products](#-premium-saas-products) - [🗄️ DynamoDB Database](#️-dynamodb-database) - [Fixit-API Access Patterns](#fixit-api-access-patterns) - [Single Table Design](#single-table-design) @@ -99,8 +100,6 @@ The Fixit API provides a robust, scalable, and secure backend for the Fixit SaaS ## 📖 API Schema - - This API exposes both **REST** and **GraphQL** endpoints: @@ -183,6 +182,7 @@ flowchart LR auth --> register("/api/auth/register \n­\n • User registration") auth --> login("/api/auth/login \n­\n • User logins via Local\n or OAuth mechanisms") auth --> googleToken("/api/auth/google-token \n­\n • User logins via Google\nOAuth2 OneTap FedCM") + auth --> pwReset("/api/auth/password-reset \n­\n • Password reset requests") auth --> token("/api/auth/token \n­\n • Refreshes auth tokens") connect --> accountLink("/api/connect/account-link \n­\n • Returns a link to the Stripe-hosted\nConnect onboarding portal") connect --> dashboardLink("/api/connect/dashboard-link \n­\n • Returns a link to the Stripe-hosted\naccount management portal") @@ -217,6 +217,16 @@ This API uses self-vended [JSON Web Tokens](https://jwt.io/introduction) to mana +## 💎 Premium SaaS Products + +The table below lists currently available Fixit SaaS products. Subscription management is powered by [Stripe](https://stripe.com/billing). + +| Product | Description | Price (USD) | Promo Code(s) Available? | +| :---------------------- | -------------------- | :---------: | :----------------------: | +| Fixit SaaS Subscription | 14-Day Free Trial | $0 | N/A | +| Fixit SaaS Subscription | Monthly Subscription | $5/month | ✓ | +| Fixit SaaS Subscription | Annual Subscription | $50/year | ✓ | + ## 🗄️ DynamoDB Database This API uses a single DynamoDB table with primary keys `pk` and `sk`, along with an overloaded `data` index attribute which supports a range of flexible queries using two GSIs: `Overloaded_SK_GSI` and `Overloaded_Data_GSI`. diff --git a/SECURITY.md b/SECURITY.md index 93bd5b38..196c14e6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ # Security Policy -### Reporting a Vulnerability: +### Reporting a Vulnerability Any effort to discover and disclose security vulnerabilities in a responsible manner is deeply appreciated. Instead of opening a GitHub Issue for security vulnerabilities, please email [trevor@nerdware.cloud](mailto:trevor@nerdware.cloud) with the bug report. diff --git a/__mocks__/@aws-sdk/client-lambda.ts b/__mocks__/@aws-sdk/client-lambda.ts index c4042b99..d99cc6be 100644 --- a/__mocks__/@aws-sdk/client-lambda.ts +++ b/__mocks__/@aws-sdk/client-lambda.ts @@ -1,13 +1,12 @@ import { mockClient } from "aws-sdk-client-mock"; -const { LambdaClient: _LambdaClient, InvokeCommand } = await vi.importActual< - typeof import("@aws-sdk/client-lambda") ->("@aws-sdk/client-lambda"); +const { LambdaClient: Actual_LambdaClient, InvokeCommand } = + await vi.importActual("@aws-sdk/client-lambda"); const LambdaClient = vi.fn(() => - mockClient(_LambdaClient) + mockClient(Actual_LambdaClient) .on(InvokeCommand) - .callsFake(({ Payload }) => ({ Payload })) + .callsFake(({ Payload }) => Promise.resolve({ Payload })) ); export { LambdaClient, InvokeCommand }; diff --git a/__mocks__/@aws-sdk/client-pinpoint.ts b/__mocks__/@aws-sdk/client-pinpoint.ts new file mode 100644 index 00000000..0a1d48a2 --- /dev/null +++ b/__mocks__/@aws-sdk/client-pinpoint.ts @@ -0,0 +1,14 @@ +import { mockClient } from "aws-sdk-client-mock"; + +const { PinpointClient: Actual_PinpointClient, SendMessagesCommand } = await vi.importActual< + typeof import("@aws-sdk/client-pinpoint") +>("@aws-sdk/client-pinpoint"); + +const PinpointClient = vi.fn( + () => + mockClient(Actual_PinpointClient) + .on(SendMessagesCommand) + .callsFake(() => Promise.resolve({})) // return an empty object +); + +export { PinpointClient, SendMessagesCommand }; diff --git a/codegen.ts b/codegen.ts index 20c76aed..9dc6fd34 100644 --- a/codegen.ts +++ b/codegen.ts @@ -20,23 +20,26 @@ const codegenConfig: CodegenConfig = { // This objects keys are Codegen output target files "src/types/__codegen__/graphql.ts": { plugins: ["typescript", "typescript-resolvers"], + // Plugin configs: config: { enumsAsTypes: true, - useIndexSignature: true, useTypeImports: true, + useIndexSignature: true, + defaultMapper: "Partial<{T}>", + // resolver context type: - contextType: "@/apolloServer.js#ApolloServerResolverContext", + contextType: "@/apolloServer.js#ApolloServerContext", scalars: { ID: "string", DateTime: "Date", Email: "string", }, + // `mappers` type overrides: mappers: { - User: "@/models/User/User.js#UserItem", Contact: "@/models/Contact/Contact.js#ContactItem", - FixitUser: "@/graphql/FixitUser/types.js#FixitUserCodegenInterface", + PublicUserFields: "@/graphql/PublicUserFields/types.js#PublicUserFieldsCodegenInterface", Invoice: "@/models/Invoice/Invoice.js#InvoiceItem", WorkOrder: "@/models/WorkOrder/WorkOrder.js#WorkOrderItem", UserSubscription: "@/models/UserSubscription/UserSubscription.js#UserSubscriptionItem", diff --git a/docker-compose.yaml b/docker-compose.yaml index 899a7da3..f6285f40 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,4 +1,3 @@ -version: "3.8" name: fixit-backend # This docker-compose file provides quick all-in-one setup for local development. diff --git a/docs/objectProperties/createdAt.yaml b/docs/objectProperties/createdAt.yaml deleted file mode 100644 index 05038f51..00000000 --- a/docs/objectProperties/createdAt.yaml +++ /dev/null @@ -1,3 +0,0 @@ -type: string -format: date-time -description: The timestamp which indicates when the resource was created. diff --git a/docs/objectProperties/email.yaml b/docs/objectProperties/email.yaml deleted file mode 100644 index f48aa3ee..00000000 --- a/docs/objectProperties/email.yaml +++ /dev/null @@ -1,3 +0,0 @@ -type: string -format: email -description: A user's email address. diff --git a/docs/objectProperties/googleIDToken.yaml b/docs/objectProperties/googleIDToken.yaml deleted file mode 100644 index 3d1a2e83..00000000 --- a/docs/objectProperties/googleIDToken.yaml +++ /dev/null @@ -1,2 +0,0 @@ -type: string -description: "A base64-encoded JSON JWT from GoogleID services (auth: google-oauth)." diff --git a/docs/responses/default.UnexpectedResponse.yaml b/docs/responses/default.UnexpectedResponse.yaml deleted file mode 100644 index a5a75cc0..00000000 --- a/docs/responses/default.UnexpectedResponse.yaml +++ /dev/null @@ -1,6 +0,0 @@ -## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response - -# Default response -# - This fallback applies if no defined response status codes match the response. - -description: Unexpected response diff --git a/docs/schemas/CspViolationReport.yaml b/docs/schemas/CspViolationReport.yaml deleted file mode 100644 index dbbb1840..00000000 --- a/docs/schemas/CspViolationReport.yaml +++ /dev/null @@ -1,64 +0,0 @@ -## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/schema - -# REST Schema: CSP Violation Report - -type: object -description: A Content Security Policy (CSP) violation report. -properties: - csp-report: - type: object - externalDocs: - description: CSP directives documentation - url: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy" - properties: - document-uri: - type: string - description: | - The URI of the protected resource that was violated. - blocked-uri: - type: string - description: | - The URI of the resource that was blocked from loading. - status-code: - type: integer - description: | - The HTTP status code of the resource that was blocked from loading. - referrer: - type: string - description: | - The referrer of the protected resource that was violated. - script-sample: - type: string - description: | - The first 40 characters of the inline script, event handler, or style - that caused the violation. - original-policy: - type: string - description: | - The original policy as specified by the Content-Security-Policy header. - disposition: - type: string - enum: [enforce, report] - description: | - Either "enforce" or "report" depending on whether the Content-Security-Policy - header or the Content-Security-Policy-Report-Only header is used. - violated-directive: - type: string - description: | - The directive whose enforcement was violated (e.g. "default-src 'self'"). - effective-directive: - type: string - description: | - The effective directive that was violated (e.g. 'img-src'). - source-file: - type: string - description: | - The URI of the resource where the violation occurred. - line-number: - type: integer - description: | - The line number in the resource where the violation occurred. - column-number: - type: integer - description: | - The column number in the resource where the violation occurred. diff --git a/docs/schemas/PreFetchedUserItems.yaml b/docs/schemas/PreFetchedUserItems.yaml deleted file mode 100644 index bc384795..00000000 --- a/docs/schemas/PreFetchedUserItems.yaml +++ /dev/null @@ -1,31 +0,0 @@ -## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/schema - -# REST Schema: PreFetchedUserItems - -type: object -description: | - A User's pre-fetched WorkOrders, Invoices, and Contacts (used on logins). When fetched - by the `queryUserItems` middleware (see `src/middleware/auth/queryUserItems.ts`), these - objects are made available on Express `res.locals` objects under the key `userItems`. - - Note: the middleware converts workOrders' and invoices' internal `createdByUserID` and - `assignedToUserID` fields into `createdBy` and `assignedTo` objects to match the GQL - schema, but only the `"id"` field can be provided on the createdBy/assignedTo objects - without fetching additional data on the associated users/contacts from either the db or - usersCache. The middleware forgoes fetching the data since the client-side Apollo cache - already handles fetching additional data as needed (_if_ it's needed), and fetching it - there can delay auth request response times, especially if the authenticating user has - a large number of items. -properties: - workOrders: - type: array - description: The user's work orders. - items: { $ref: "../open-api.yaml#/components/schemas/WorkOrder" } - invoices: - type: array - description: The user's invoices. - items: { $ref: "../open-api.yaml#/components/schemas/Invoice" } - contacts: - type: array - description: The user's contacts. - items: { $ref: "../open-api.yaml#/components/schemas/Contact" } diff --git a/docs/schemas/SubscriptionPriceName.yaml b/docs/schemas/SubscriptionPriceName.yaml deleted file mode 100644 index 7fd42026..00000000 --- a/docs/schemas/SubscriptionPriceName.yaml +++ /dev/null @@ -1,9 +0,0 @@ -## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/schema - -# REST Schema: SubscriptionPriceName - -type: string -enum: [ANNUAL, MONTHLY, TRIAL] -description: | - A Fixit subscription price name — this value corresponds to the [Stripe Price - "nickname" field](https://stripe.com/docs/api/prices/object#price_object-nickname). diff --git a/docs/schemas/UserProfileParams.yaml b/docs/schemas/UserProfileParams.yaml deleted file mode 100644 index 75a1df1c..00000000 --- a/docs/schemas/UserProfileParams.yaml +++ /dev/null @@ -1,12 +0,0 @@ -## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/schema - -# REST Schema: UserProfileParams - -type: object -description: Parameters for a user's profile. -properties: - displayName: { type: string, description: The user's display name. } - familyName: { type: string, description: The user's family name. } - givenName: { type: string, description: The user's given name. } - businessName: { type: string, description: The user's business name. } - photoUrl: { type: string, description: The user's profile picture URL. } diff --git a/eslint.config.js b/eslint.config.js index 1a5e8745..0ef469df 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -48,48 +48,45 @@ export default [ ...tsEslint.configs.stylisticTypeChecked, // prettier-ignore ].reduce((acc, { rules = {} }) => ({ ...acc, ...rules }), {}), // RULE CUSTOMIZATIONS: - "default-case": "error", - "default-case-last": "error", + "default-case": "error", // switch-case statements must have a default case + "default-case-last": "error", // switch-case statements' default case must be last eqeqeq: ["error", "always"], "no-console": "warn", - "prefer-const": "warn", + "prefer-const": ["warn", { destructuring: "all" }], "prefer-object-has-own": "error", "prefer-promise-reject-errors": "error", semi: ["error", "always"], "import/named": "off", // TS performs this check "import/namespace": "off", // TS performs this check "import/default": "off", // TS performs this check - "import/no-named-as-default": "off", // TS performs this check + "import/no-named-as-default": "off", // TS performs this check "import/no-named-as-default-member": "off", // TS performs this check - "node/no-missing-import": "off", + "node/no-missing-import": "off", // Does not work with path aliases "node/no-process-env": "error", - "node/no-unpublished-import": "off", - "node/no-unsupported-features/es-syntax": "off", - "@typescript-eslint/array-type": "off", // Allow "T[]" and "Array" + "node/no-unpublished-import": ["error", { allowModules: ["type-fest"] }], + "node/no-unsupported-features/es-syntax": "off", // Allow dynamic import (no config option) + "@typescript-eslint/array-type": "off", // Allow "T[]" and "Array" "@typescript-eslint/consistent-indexed-object-style": "off", // Allow "Record" and "{ [key: K]: V }" - "@typescript-eslint/consistent-type-definitions": "off", // Allow "type" and "interface", there are subtle usage differences - "@typescript-eslint/no-confusing-void-expression": "off", // <-- rule results in false positives on MW using `return next()` - "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/consistent-type-definitions": "off", // Allow "type" and "interface", there are subtle usage differences + "@typescript-eslint/no-confusing-void-expression": "off", // Allow 1-line arrow fns to return void for readability + "@typescript-eslint/no-explicit-any": ["error", { ignoreRestArgs: true }], "@typescript-eslint/no-extraneous-class": ["error", { allowStaticOnly: true }], "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-invalid-void-type": "off", // Allow "void" in unions "@typescript-eslint/no-misused-promises": [ "error", { checksVoidReturn: { arguments: false } }, ], "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-unnecessary-boolean-literal-compare": "off", // Allow "if (x === true)" - "@typescript-eslint/no-unnecessary-condition": "off", // Allow option chains to convey "dont know if preceding exists" - "@typescript-eslint/no-unsafe-argument": "off", - "@typescript-eslint/no-unsafe-member-access": "off", "@typescript-eslint/no-unused-vars": [ "warn", { - vars: "all", - varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_", - ignoreRestSiblings: true, + vars: "all", + varsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + ignoreRestSiblings: false, }, ], "@typescript-eslint/prefer-for-of": "off", @@ -100,7 +97,18 @@ export default [ ignorePrimitives: { string: true }, }, ], - "@typescript-eslint/prefer-reduce-type-parameter": "off", + "@typescript-eslint/restrict-template-expressions": [ + "error", + { + allowAny: false, + allowNever: false, + allowArray: true, + allowBoolean: true, + allowNullish: true, + allowNumber: true, + allowRegExp: true, + }, + ], ...eslintConfigPrettier.rules, // <-- must be last, removes rules that conflict with prettier }, settings: { @@ -128,18 +136,23 @@ export default [ vitest: vitestPlugin, }, rules: { - ...vitestPlugin.configs.all.rules, + ...vitestPlugin.configs.recommended.rules, "vitest/consistent-test-it": ["error", { fn: "test" }], - "vitest/max-expects": "off", - "vitest/no-conditional-expect": "off", "vitest/no-disabled-tests": "warn", - "vitest/no-focused-tests": "warn", - "vitest/no-hooks": "off", - "vitest/prefer-expect-assertions": "off", + "vitest/no-focused-tests": ["warn", { fixable: false }], "vitest/prefer-lowercase-title": ["error", { ignore: ["describe"] }], + "vitest/prefer-to-be-truthy": "off", + "vitest/prefer-to-be-falsy": "off", "vitest/valid-expect": "warn", + "node/no-unpublished-import": [ + "error", + { allowModules: ["type-fest", "vitest", "supertest", "@graphql-tools/mock"] }, + ], "@typescript-eslint/no-confusing-void-expression": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unsafe-argument": "off", "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-member-access": "off", }, }, //////////////////////////////////////////////////////////////// diff --git a/fixit@current.graphql b/fixit@current.graphql deleted file mode 100644 index 1dfad689..00000000 --- a/fixit@current.graphql +++ /dev/null @@ -1,505 +0,0 @@ -type Query { - _root: Boolean - contact(contactID: ID!): Contact! - myContacts: [Contact!]! - invoice(invoiceID: ID!): Invoice! - myInvoices: MyInvoicesQueryReturnType! - myProfile: Profile! - profile(profileID: ID!): Profile! - user: User! - - """ - This query returns the public fields of a User whose handle exactly matches the - provided `handle` argument. To search for one or more Users whose handle begins - with or fuzzy-matches a provided string, use `searchForUsersByHandle`. - """ - getUserByHandle(handle: String!): Contact - - """ - This query returns a paginated list of Users whose handle begins with the provided - `handle` argument, which can be incomplete but must at least contain two characters: - the beginning "@", and one character that's either alphanumeric or an underscore. - - Note that this query is intended to be used in conjunction with a pagination utility - like [Apollo's `fetchMore` function](https://www.apollographql.com/docs/react/pagination/core-api#the-fetchmore-function). - - ### ROADMAP: - - - Matching Algorithm Change: In the future, the Contact selection method used in this - query will either be replaced by a fuzzy-matching system based on the Levenshtein-Demerau - model, or a cloud-based search service like ElasticSearch. This change will eliminate - the `offset` restrictions in regard to the value of `handle` in follow-up queries. - - Response Structure: The response may be converted into an object with keys `data` and - `nextOffset`. The `data` key will contain the array of matching Users, and `nextOffset` - will be the value of the `offset` argument to be used in a follow-up query. - """ - searchForUsersByHandle( - """The handle search string (minimum 2 characters)""" - handle: String! - - """The maximum number of Users to return (default 10, min 10, max 50)""" - limit: Int = 10 - - """ - The number of searchable Users to skip before returning results (default 0, min 0). - **This argument should only be used if all of the following conditions are true:** - - 1. A previous call to this query returned the maximum number of results (i.e., `limit`). - 2. The User who made the previous call wants to retrieve more results. - 3. The `handle` argument in the previous call is a valid substring of the `handle` - argument in the subsequent call (e.g., "@foo" followed by "@fooz"). While not enforced, - querying "@fooz" followed by "@foo" with an offset may result in matchable users being - excluded from the results. - """ - offset: Int = 0 - ): [Contact!]! - mySubscription: UserSubscription! - workOrder(workOrderID: ID!): WorkOrder! - myWorkOrders: MyWorkOrdersQueryReturnType! -} - -type Mutation { - _root: Boolean - createContact(contactUserID: ID!): Contact! - deleteContact(contactID: ID!): DeleteMutationResponse! - createInvoice(invoice: InvoiceInput!): Invoice! - updateInvoiceAmount(invoiceID: ID!, amount: Int!): Invoice! - payInvoice(invoiceID: ID!): Invoice! - deleteInvoice(invoiceID: ID!): DeleteMutationResponse! - updateProfile(profile: ProfileInput!): Profile! - createWorkOrder(workOrder: CreateWorkOrderInput!): WorkOrder! - updateWorkOrder(workOrderID: ID!, workOrder: UpdateWorkOrderInput!): WorkOrder! - cancelWorkOrder(workOrderID: ID!): CancelWorkOrderResponse! - setWorkOrderStatusComplete(workOrderID: ID!): WorkOrder! - createInvite(phoneOrEmail: String!): GenericSuccessResponse! -} - -""" -Custom DateTime scalar with handling for Date objects and datetime strings -""" -scalar DateTime - -"""Custom Email scalar with regex validation""" -scalar Email - -type DeleteMutationResponse { - id: ID! - wasDeleted: Boolean! -} - -""" -Generic response-type for mutations which simply returns a "wasSuccessful" boolean. -This is only ever used as a "last-resort" response-type for mutations which meet all -of the following criteria: - 1. The mutation does not perform any database CRUD operations. - 2. The mutation does not perform any CRUD operations on data maintained by the client-side cache. - 3. No other response-type is appropriate for the mutation. - -Typically the only mutations for which this reponse-type is appropriate are those which -perform some sort of "side-effect" (e.g. sending an email, sending a text message, etc.). -""" -type GenericSuccessResponse { - wasSuccessful: Boolean! -} - -""" -FixitUser is an interface which defines publicly-accessible User fields. This -interface has two concrete implementations: Contact, which is simply a concrete -implementation of the same publicly-available fields, and User, which adds private -fields which are not accessible to other users. -""" -interface FixitUser { - """User ID internally identifies individual User accounts""" - id: ID! - - """Public-facing handle identifies users to other users (e.g., '@joe')""" - handle: String! - - """Email address of either a User or Contact""" - email: Email! - - """Phone number of either a User or Contact""" - phone: String - - """Profile object of either a User or Contact""" - profile: Profile! - createdAt: DateTime! - updatedAt: DateTime! -} - -type ChecklistItem { - id: ID! - description: String! - isCompleted: Boolean! -} - -input CreateChecklistItemInput { - description: String! - isCompleted: Boolean -} - -input UpdateChecklistItemInput { - id: ID - description: String! - isCompleted: Boolean -} - -""" -Contact is a type which is simply a concrete implementation of the publicly -accessible User fields defined in the FixitUser interface. The Contact type is -meant to ensure that private User fields are not available to anyone other than -the User who owns the data. -""" -type Contact implements FixitUser { - """Contact ID internally identifies a user's contact""" - id: ID! - - """Public-facing handle identifies users to other users (e.g., '@joe')""" - handle: String! - - """Contact email address""" - email: Email! - - """Contact phone number""" - phone: String - - """Contact Profile object""" - profile: Profile! - - """(Immutable) Contact creation timestamp""" - createdAt: DateTime! - - """Timestamp of the most recent Contact object update""" - updatedAt: DateTime! -} - -type Invoice { - """ - (Immutable) Invoice ID, in the format of 'INV#{createdBy.id}#{unixTimestampUUID(createdAt)}' - """ - id: ID! - - """(Immutable) The FixitUser who created/sent the Invoice""" - createdBy: FixitUser! - - """ - (Immutable) The FixitUser to whom the Invoice was assigned, AKA the Invoice's recipient - """ - assignedTo: FixitUser! - - """ - The Invoice amount, represented as an integer which reflects USD centage (i.e., an 'amount' of 1 = $0.01 USD) - """ - amount: Int! - - """ - The Invoice status; this field is controlled by the API and can not be directly edited by Users - """ - status: InvoiceStatus! - - """ - The ID of the most recent successful paymentIntent applied to the Invoice, if any - """ - stripePaymentIntentID: String - - """ - A WorkOrder attached to the Invoice which was created by the 'assignedTo' User - """ - workOrder: WorkOrder - - """(Immutable) Invoice creation timestamp""" - createdAt: DateTime! - - """Timestamp of the most recent Invoice update""" - updatedAt: DateTime! -} - -enum InvoiceStatus { - OPEN - CLOSED - DISPUTED -} - -type MyInvoicesQueryReturnType { - createdByUser: [Invoice!]! - assignedToUser: [Invoice!]! -} - -input InvoiceInput { - """The ID of the User to whom the Invoice will be assigned""" - assignedTo: ID! - amount: Int! - workOrderID: ID -} - -type Location { - streetLine1: String! - streetLine2: String - city: String! - region: String! - country: String! -} - -input CreateLocationInput { - streetLine1: String! - streetLine2: String - city: String! - region: String! - country: String -} - -input UpdateLocationInput { - streetLine1: String! - streetLine2: String - city: String! - region: String! - country: String -} - -type Profile { - displayName: String! - givenName: String - familyName: String - businessName: String - photoUrl: String -} - -input ProfileInput { - displayName: String - givenName: String - familyName: String - businessName: String - photoUrl: String -} - -type UserStripeConnectAccount { - id: ID! - detailsSubmitted: Boolean! - chargesEnabled: Boolean! - payoutsEnabled: Boolean! - createdAt: DateTime! - updatedAt: DateTime! -} - -""" -User is an implementation of the FixitUser interface which includes both the -publicly-accessible FixitUser/Contact fields as well as private fields which -are only accessible by the User who owns the data. -""" -type User implements FixitUser { - """(Immutable) User ID internally identifies individual User accounts""" - id: ID! - - """ - (Immutable) Public-facing handle identifies users to other users (e.g., '@joe') - """ - handle: String! - - """(Immutable) User's own email address""" - email: Email! - - """User's own phone number""" - phone: String - - """ - (Mobile-Only) User's Expo push token, used to send push notifications to the User's mobile device - """ - expoPushToken: String - - """User's own Profile object""" - profile: Profile! - - """User's Stripe Customer ID (defined and generated by Stripe)""" - stripeCustomerID: String! - - """User Subscription info""" - subscription: UserSubscription - - """User Stripe Connect Account info""" - stripeConnectAccount: UserStripeConnectAccount - - """(Immutable) Account creation timestamp""" - createdAt: DateTime! - - """Timestamp of the most recent account update""" - updatedAt: DateTime! -} - -type UserSubscription { - id: ID! - currentPeriodEnd: DateTime! - productID: String! - priceID: String! - status: SubscriptionStatus! - createdAt: DateTime! - updatedAt: DateTime! -} - -enum SubscriptionStatus { - active - incomplete - incomplete_expired - trialing - past_due - canceled - unpaid -} - -enum SubscriptionPriceLabel { - ANNUAL - MONTHLY - TRIAL -} - -""" -A WorkOrder is a request one User submits to another for work to be performed at a location -""" -type WorkOrder { - """ - (Immutable) WorkOrder ID, in the format of 'WO#{createdBy.id}#{unixTimestampUUID(createdAt)}' - """ - id: ID! - - """(Immutable) The FixitUser who created/sent the WorkOrder""" - createdBy: FixitUser! - - """ - The FixitUser to whom the WorkOrder was assigned, AKA the WorkOrder's recipient - """ - assignedTo: FixitUser - - """The WorkOrder status""" - status: WorkOrderStatus! - - """The WorkOrder priority""" - priority: WorkOrderPriority! - - """The location of the job site for the WorkOrder""" - location: Location! - - """The category of work to be performed as part of the WorkOrder""" - category: WorkOrderCategory - - """ - A general description of the work to be performed as part of the WorkOrder - """ - description: String - - """The WorkOrder checklist, an array of ChecklistItem objects""" - checklist: [ChecklistItem] - - """Timestamp of the WorkOrder's due date""" - dueDate: DateTime - - """The name of the WorkOrder's entry contact, if any""" - entryContact: String - - """The phone number of the WorkOrder's entry contact, if any""" - entryContactPhone: String - - """Timestamp of the WorkOrder's scheduled completion""" - scheduledDateTime: DateTime - - """ - Notes from the WorkOrder's recipient (this field will be renamed in the future) - """ - contractorNotes: String - - """(Immutable) WorkOrder creation timestamp""" - createdAt: DateTime! - - """Timestamp of the most recent WorkOrder update""" - updatedAt: DateTime! -} - -enum WorkOrderStatus { - UNASSIGNED - ASSIGNED - IN_PROGRESS - DEFERRED - CANCELLED - COMPLETE -} - -enum WorkOrderPriority { - LOW - NORMAL - HIGH -} - -enum WorkOrderCategory { - DRYWALL - ELECTRICAL - FLOORING - GENERAL - HVAC - LANDSCAPING - MASONRY - PAINTING - PAVING - PEST - PLUMBING - ROOFING - TRASH - TURNOVER - WINDOWS -} - -type MyWorkOrdersQueryReturnType { - createdByUser: [WorkOrder!]! - assignedToUser: [WorkOrder!]! -} - -input CreateWorkOrderInput { - assignedTo: ID - priority: WorkOrderPriority - location: CreateLocationInput! - category: WorkOrderCategory - description: String - checklist: [CreateChecklistItemInput!] - dueDate: DateTime - entryContact: String - entryContactPhone: String - scheduledDateTime: DateTime -} - -input UpdateWorkOrderInput { - assignedToUserID: ID - priority: WorkOrderPriority - location: UpdateLocationInput - category: WorkOrderCategory - description: String - checklist: [UpdateChecklistItemInput!] - dueDate: DateTime - entryContact: String - entryContactPhone: String - scheduledDateTime: DateTime -} - -union CancelWorkOrderResponse = WorkOrder | DeleteMutationResponse - -type AuthTokenPayload { - id: ID! - handle: String! - email: String! - phone: String! - profile: Profile! - stripeCustomerID: String! - subscription: AuthTokenPayloadSubscriptionInfo - stripeConnectAccount: AuthTokenPayloadStripeConnectAccountInfo! - createdAt: DateTime! - updatedAt: DateTime! -} - -type AuthTokenPayloadSubscriptionInfo { - id: ID! - status: SubscriptionStatus! - currentPeriodEnd: DateTime! -} - -type AuthTokenPayloadStripeConnectAccountInfo { - id: ID! - detailsSubmitted: Boolean! - chargesEnabled: Boolean! - payoutsEnabled: Boolean! -} diff --git a/src/loader.js b/loader.js similarity index 100% rename from src/loader.js rename to loader.js diff --git a/nodemon.json b/nodemon.json index a37a89d0..aadc87c1 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,6 +1,6 @@ { "execMap": { - "ts": "node -r ts-node/register --loader ./src/loader.js --no-warnings" + "ts": "node -r ts-node/register --loader ./loader.js --no-warnings" }, "ext": "ts,js", "watch": ["src"], diff --git a/package-lock.json b/package-lock.json index 0605ce6a..e0c4a29e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,91 +1,85 @@ { "name": "fixit-api", - "version": "2.1.4", + "version": "2.2.0-next.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fixit-api", - "version": "2.1.4", + "version": "2.2.0-next.1", "license": "LicenseRef-LICENSE", "dependencies": { - "@apollo/server": "^4.10.1", - "@aws-sdk/client-dynamodb": "^3.391.0", - "@aws-sdk/client-lambda": "^3.391.0", - "@aws-sdk/lib-dynamodb": "^3.391.0", - "@graphql-tools/schema": "^10.0.0", - "@nerdware/ddb-single-table": "^2.4.0", - "@nerdware/ts-string-helpers": "^1.2.1", - "@nerdware/ts-type-safety-utils": "^1.0.8", - "@sentry/node": "^7.64.0", + "@apollo/server": "^4.10.5", + "@aws-sdk/client-dynamodb": "^3.623.0", + "@aws-sdk/client-lambda": "^3.623.0", + "@aws-sdk/client-pinpoint": "^3.623.0", + "@aws-sdk/lib-dynamodb": "^3.623.0", + "@graphql-tools/schema": "^10.0.4", + "@graphql-tools/utils": "^10.3.2", + "@nerdware/ddb-single-table": "^2.6.3", + "@nerdware/ts-string-helpers": "^1.7.0", + "@nerdware/ts-type-safety-utils": "^1.0.14", + "@sentry/node": "^7.118.0", "bcrypt": "^5.1.1", "chalk": "^5.3.0", "cors": "^2.8.5", - "dayjs": "^1.11.9", - "expo-server-sdk": "^3.7.0", + "dayjs": "^1.11.12", + "expo-server-sdk": "^3.10.0", "express": "^4.19.2", - "google-auth-library": "^9.7.0", - "graphql": "^16.8.1", - "graphql-tag": "^2.12.6", - "helmet": "^7.0.0", - "jsonwebtoken": "^9.0.1", + "google-auth-library": "^9.13.0", + "graphql": "^16.9.0", + "helmet": "^7.1.0", + "jsonwebtoken": "^9.0.2", "lodash.merge": "^4.6.2", - "stripe": "^10.7.0", - "uuid": "^9.0.0" + "stripe": "^10.17.0", + "uuid": "^10.0.0", + "zod": "^3.23.8" }, "devDependencies": { - "@apollo/rover": "^0.22.0", - "@graphql-codegen/cli": "^5.0.0", - "@graphql-codegen/introspection": "^4.0.0", - "@graphql-codegen/typescript": "^4.0.1", - "@graphql-codegen/typescript-resolvers": "^4.0.1", - "@graphql-tools/mock": "^9.0.0", + "@apollo/rover": "^0.23.0", + "@graphql-codegen/cli": "^5.0.2", + "@graphql-codegen/introspection": "^4.0.3", + "@graphql-codegen/typescript": "^4.0.9", + "@graphql-codegen/typescript-resolvers": "^4.2.1", + "@graphql-tools/mock": "^9.0.4", + "@redocly/cli": "^1.19.0", "@serverless-guru/prettier-plugin-import-order": "^0.4.2", - "@swc/cli": "^0.3.10", - "@swc/core": "^1.3.82", - "@types/bcrypt": "^5.0.0", - "@types/cors": "^2.8.13", - "@types/express": "^4.17.17", - "@types/jsonwebtoken": "^9.0.2", - "@types/lodash.merge": "^4.6.7", - "@types/node": "^20.5.0", + "@swc/cli": "^0.3.14", + "@swc/core": "^1.7.5", + "@types/bcrypt": "^5.0.2", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.6", + "@types/lodash.merge": "^4.6.9", + "@types/node": "^20.14.14", "@types/supertest": "^6.0.2", - "@types/uuid": "^9.0.2", - "@vitest/coverage-v8": "^1.3.1", - "aws-sdk-client-mock": "^3.0.0", - "eslint": "^8.47.0", + "@types/uuid": "^9.0.8", + "@vitest/coverage-v8": "^1.6.0", + "aws-sdk-client-mock": "^4.0.1", + "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", - "eslint-import-resolver-typescript": "^3.6.0", - "eslint-plugin-import": "^2.28.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-vitest": "^0.3.1", - "nodemon": "^3.0.1", - "openapi-typescript": "^7.0.0-next.7", - "prettier": "^3.2.5", - "supertest": "^6.3.3", - "swagger-cli": "^4.0.4", + "eslint-plugin-vitest": "^0.5.4", + "nodemon": "^3.1.4", + "openapi-typescript": "^7.3.0", + "prettier": "^3.3.3", + "stripe-event-types": "^3.1.0", + "supertest": "^7.0.0", "swaggerhub-cli": "^0.9.0", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "type-fest": "^4.2.0", - "typescript": "^5.1.6", - "typescript-eslint": "^7.1.1", - "vite": "^5.1.7", - "vite-tsconfig-paths": "^4.2.0", - "vitest": "^1.3.1", + "type-fest": "^4.23.0", + "typescript": "^5.5.4", + "typescript-eslint": "^7.18.0", + "vite": "^5.3.5", + "vite-tsconfig-paths": "^4.3.2", + "vitest": "^1.5.2", "vitest-github-actions-reporter": "^0.11.1" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">=20.0.0" } }, "node_modules/@actions/core": { @@ -93,6 +87,7 @@ "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz", "integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==", "dev": true, + "license": "MIT", "dependencies": { "@actions/http-client": "^2.0.1", "uuid": "^8.3.2" @@ -103,6 +98,7 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -112,6 +108,7 @@ "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.1.tgz", "integrity": "sha512-KhC/cZsq7f8I4LfZSJKgCvEwfkE8o1538VoBeoGzokVLLnbFDEAdFD3UhoMklxo2un9NJVBdANOresx7vTHlHw==", "dev": true, + "license": "MIT", "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" @@ -122,6 +119,7 @@ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -130,273 +128,11 @@ "node": ">=6.0.0" } }, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz", - "integrity": "sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==", - "dev": true, - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "call-me-maybe": "^1.0.1", - "js-yaml": "^3.13.1" - } - }, - "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@apidevtools/openapi-schemas": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", - "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@apidevtools/swagger-cli": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@apidevtools/swagger-cli/-/swagger-cli-4.0.4.tgz", - "integrity": "sha512-hdDT3B6GLVovCsRZYDi3+wMcB1HfetTU20l2DC8zD3iFRNMC6QNAZG5fo/6PYeHWBEv7ri4MvnlKodhNB0nt7g==", - "deprecated": "This package has been abandoned. Please switch to using the actively maintained @redocly/cli", - "dev": true, - "dependencies": { - "@apidevtools/swagger-parser": "^10.0.1", - "chalk": "^4.1.0", - "js-yaml": "^3.14.0", - "yargs": "^15.4.1" - }, - "bin": { - "swagger-cli": "bin/swagger-cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@apidevtools/swagger-cli/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@apidevtools/swagger-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@apidevtools/swagger-cli/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/@apidevtools/swagger-cli/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==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@apidevtools/swagger-cli/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@apidevtools/swagger-cli/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==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@apidevtools/swagger-cli/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, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@apidevtools/swagger-cli/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==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@apidevtools/swagger-cli/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/@apidevtools/swagger-cli/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@apidevtools/swagger-cli/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==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@apidevtools/swagger-methods": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", - "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", - "dev": true - }, - "node_modules/@apidevtools/swagger-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.1.0.tgz", - "integrity": "sha512-9Kt7EuS/7WbMAUv2gSziqjvxwDbFSg3Xeyfuj5laUODX8o/k/CpsAKiQ8W7/R88eXFTMbJYg6+7uAmOWNKmwnw==", - "dev": true, - "dependencies": { - "@apidevtools/json-schema-ref-parser": "9.0.6", - "@apidevtools/openapi-schemas": "^2.1.0", - "@apidevtools/swagger-methods": "^3.0.2", - "@jsdevtools/ono": "^7.1.3", - "ajv": "^8.6.3", - "ajv-draft-04": "^1.0.0", - "call-me-maybe": "^1.0.1" - }, - "peerDependencies": { - "openapi-types": ">=7" - } - }, - "node_modules/@apidevtools/swagger-parser/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@apidevtools/swagger-parser/node_modules/ajv-draft-04": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", - "dev": true, - "peerDependencies": { - "ajv": "^8.5.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/@apidevtools/swagger-parser/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==", - "dev": true - }, "node_modules/@apollo/cache-control-types": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@apollo/cache-control-types/-/cache-control-types-1.0.3.tgz", "integrity": "sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g==", + "license": "MIT", "peerDependencies": { "graphql": "14.x || 15.x || 16.x" } @@ -406,6 +142,7 @@ "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.7.tgz", "integrity": "sha512-Lahx5zntHPZia35myYDBRuF58tlwPskwHc5CWBZC/4bMKB6siTBWwtMrkqXcsNwQiFSzSx5hKdRPUmemrEp3Gg==", "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -426,16 +163,18 @@ } }, "node_modules/@apollo/rover": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@apollo/rover/-/rover-0.22.0.tgz", - "integrity": "sha512-ZtdSu6vT5qPPM937sRYHiCUsfeBoqclUp9JgLXVat9Qtllx5CWr1JpKlVYR3Q3huCJH1PifO/leIhzlvbDB0/w==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@apollo/rover/-/rover-0.23.0.tgz", + "integrity": "sha512-RnxyPJtK7VD25qQLQPwSLdBhAgGIe6JJaVf3/YPDChiP+pN+83ZQfjgmHkuaefjabD51dE+i1odCf7G99sNauw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "dependencies": { + "axios": "^1.6.5", "axios-proxy-builder": "^0.1.1", - "binary-install": "^1.0.6", "console.table": "^0.10.0", - "detect-libc": "^2.0.0" + "detect-libc": "^2.0.0", + "tar": "^6.2.0" }, "bin": { "rover": "run.js" @@ -446,9 +185,10 @@ } }, "node_modules/@apollo/server": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.10.1.tgz", - "integrity": "sha512-XGMOgTyzV4EBHQq0xQVKFry9hZF7AA/6nxxGLamqdxodhdSdGbU9jrlb5/XDveeGuXP3+5JDdrB2HcziVLJcMA==", + "version": "4.10.5", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.10.5.tgz", + "integrity": "sha512-I+Oi1CnphXExSAAsZbclgRDN4k4CEMxyKRzYg3bir5N8OmydEhzXDlIGAOETP/TKblxR7HPXGvwn2cJKzbl46w==", + "license": "MIT", "dependencies": { "@apollo/cache-control-types": "^1.0.3", "@apollo/server-gateway-interface": "^1.1.1", @@ -461,7 +201,6 @@ "@apollo/utils.usagereporting": "^2.1.0", "@apollo/utils.withrequired": "^2.0.0", "@graphql-tools/schema": "^9.0.0", - "@josephg/resolvable": "^1.0.0", "@types/express": "^4.17.13", "@types/express-serve-static-core": "^4.17.30", "@types/node-fetch": "^2.6.1", @@ -487,6 +226,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@apollo/server-gateway-interface/-/server-gateway-interface-1.1.1.tgz", "integrity": "sha512-pGwCl/po6+rxRmDMFgozKQo2pbsSwE91TpsDBAOgf74CRDPXHHtM88wbwjab0wMMZh95QfR45GGyDIdhY24bkQ==", + "license": "MIT", "dependencies": { "@apollo/usage-reporting-protobuf": "^4.1.1", "@apollo/utils.fetcher": "^2.0.0", @@ -501,6 +241,7 @@ "version": "8.4.2", "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.2.tgz", "integrity": "sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==", + "license": "MIT", "dependencies": { "@graphql-tools/utils": "^9.2.1", "tslib": "^2.4.0" @@ -513,6 +254,7 @@ "version": "9.0.19", "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz", "integrity": "sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==", + "license": "MIT", "dependencies": { "@graphql-tools/merge": "^8.4.1", "@graphql-tools/utils": "^9.2.1", @@ -527,6 +269,7 @@ "version": "9.2.1", "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", + "license": "MIT", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "tslib": "^2.4.0" @@ -535,10 +278,24 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, + "node_modules/@apollo/server/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@apollo/usage-reporting-protobuf": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz", "integrity": "sha512-u40dIUePHaSKVshcedO7Wp+mPiZsaU6xjv9J+VyxpoU/zL6Jle+9zWeG98tr/+SZ0nZ4OXhrbb8SNr0rAPpIDA==", + "license": "MIT", "dependencies": { "@apollo/protobufjs": "1.2.7" } @@ -547,6 +304,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@apollo/utils.createhash/-/utils.createhash-2.0.1.tgz", "integrity": "sha512-fQO4/ZOP8LcXWvMNhKiee+2KuKyqIcfHrICA+M4lj/h/Lh1H10ICcUtk6N/chnEo5HXu0yejg64wshdaiFitJg==", + "license": "MIT", "dependencies": { "@apollo/utils.isnodelike": "^2.0.1", "sha.js": "^2.4.11" @@ -559,6 +317,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-2.0.1.tgz", "integrity": "sha512-EsPIBqsSt2BwDsv8Wu76LK5R1KtsVkNoO4b0M5aK0hx+dGg9xJXuqlr7Fo34Dl+y83jmzn+UvEW+t1/GP2melA==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -570,6 +329,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@apollo/utils.fetcher/-/utils.fetcher-2.0.1.tgz", "integrity": "sha512-jvvon885hEyWXd4H6zpWeN3tl88QcWnHp5gWF5OPF34uhvoR+DFqcNxs9vrRaBBSY3qda3Qe0bdud7tz2zGx1A==", + "license": "MIT", "engines": { "node": ">=14" } @@ -578,6 +338,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@apollo/utils.isnodelike/-/utils.isnodelike-2.0.1.tgz", "integrity": "sha512-w41XyepR+jBEuVpoRM715N2ZD0xMD413UiJx8w5xnAZD2ZkSJnMJBoIzauK83kJpSgNuR6ywbV29jG9NmxjK0Q==", + "license": "MIT", "engines": { "node": ">=14" } @@ -586,6 +347,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-2.1.1.tgz", "integrity": "sha512-qVo5PvUUMD8oB9oYvq4ViCjYAMWnZ5zZwEjNF37L2m1u528x5mueMlU+Cr1UinupCgdB78g+egA1G98rbJ03Vw==", + "license": "MIT", "dependencies": { "@apollo/utils.logger": "^2.0.1", "lru-cache": "^7.14.1" @@ -598,6 +360,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-2.0.1.tgz", "integrity": "sha512-YuplwLHaHf1oviidB7MxnCXAdHp3IqYV8n0momZ3JfLniae92eYqMIx+j5qJFX6WKJPs6q7bczmV4lXIsTu5Pg==", + "license": "MIT", "engines": { "node": ">=14" } @@ -606,6 +369,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-2.0.1.tgz", "integrity": "sha512-9M4LUXV/fQBh8vZWlLvb/HyyhjJ77/I5ZKu+NBWV/BmYGyRmoEP9EVAy7LCVoY3t8BDcyCAGfxJaLFCSuQkPUg==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -617,6 +381,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@apollo/utils.removealiases/-/utils.removealiases-2.0.1.tgz", "integrity": "sha512-0joRc2HBO4u594Op1nev+mUF6yRnxoUH64xw8x3bX7n8QBDYdeYgY4tF0vJReTy+zdn2xv6fMsquATSgC722FA==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -628,6 +393,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@apollo/utils.sortast/-/utils.sortast-2.0.1.tgz", "integrity": "sha512-eciIavsWpJ09za1pn37wpsCGrQNXUhM0TktnZmHwO+Zy9O4fu/WdB4+5BvVhFiZYOXvfjzJUcc+hsIV8RUOtMw==", + "license": "MIT", "dependencies": { "lodash.sortby": "^4.7.0" }, @@ -642,6 +408,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-2.0.1.tgz", "integrity": "sha512-QJs7HtzXS/JIPMKWimFnUMK7VjkGQTzqD9bKD1h3iuPAqLsxd0mUNVbkYOPTsDhUKgcvUOfOqOJWYohAKMvcSA==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -653,6 +420,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@apollo/utils.usagereporting/-/utils.usagereporting-2.1.0.tgz", "integrity": "sha512-LPSlBrn+S17oBy5eWkrRSGb98sWmnEzo3DPTZgp8IQc8sJe0prDgDuppGq4NeQlpoqEHz0hQeYHAOA0Z3aQsxQ==", + "license": "MIT", "dependencies": { "@apollo/usage-reporting-protobuf": "^4.1.0", "@apollo/utils.dropunuseddefinitions": "^2.0.1", @@ -672,6 +440,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@apollo/utils.withrequired/-/utils.withrequired-2.0.1.tgz", "integrity": "sha512-YBDiuAX9i1lLc6GeTy1m7DGLFn/gMnvXqlalOIMjM7DeOgIacEjjfwPqb0M1CQ2v11HhR15d1NmxJoRCfrNqcA==", + "license": "MIT", "engines": { "node": ">=14" } @@ -681,6 +450,7 @@ "resolved": "https://registry.npmjs.org/@ardatan/relay-compiler/-/relay-compiler-12.0.0.tgz", "integrity": "sha512-9anThAaj1dQr6IGmzBMcfzOQKTa5artjuPmw8NYK/fiGEMjADbSguBY2FMDykt+QhilR3wc9VA/3yVju7JHg7Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.14.0", "@babel/generator": "^7.14.0", @@ -712,6 +482,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -728,6 +499,7 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -739,6 +511,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -752,6 +525,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -764,6 +538,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -779,6 +554,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -790,13 +566,15 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/@ardatan/relay-compiler/node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -819,6 +597,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, + "license": "ISC", "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -832,6 +611,7 @@ "resolved": "https://registry.npmjs.org/@ardatan/sync-fetch/-/sync-fetch-0.0.1.tgz", "integrity": "sha512-xhlTqH0m31mnsG0tIP4ETgfSB6gXDaYYsUWTrlUV93fFQPI9dd8hE0Ot6MHLCtqgB32hwJAC3YZMWlXZw7AleA==", "dev": true, + "license": "MIT", "dependencies": { "node-fetch": "^2.6.1" }, @@ -840,702 +620,853 @@ } }, "node_modules/@aws-crypto/crc32": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", - "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-crypto/crc32/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/ie11-detection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", - "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^1.11.1" + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", - "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/@aws-crypto/sha256-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", - "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", - "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^1.11.1" + "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/@aws-crypto/util": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", - "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/@aws-sdk/client-dynamodb": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.535.0.tgz", - "integrity": "sha512-Z4xP7uryereaLm2Q6oAZLOb6uK7IcQfsJhIm5ZGNT5bWX2W8gRf+A+J9whBYMeuTB5pDUGK3SbFi+TXZjlEmgg==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.535.0", - "@aws-sdk/core": "3.535.0", - "@aws-sdk/credential-provider-node": "3.535.0", - "@aws-sdk/middleware-endpoint-discovery": "3.535.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.535.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.535.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.2.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "@smithy/util-waiter": "^2.2.0", + "version": "3.623.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.623.0.tgz", + "integrity": "sha512-vNrU3TdMPqhwVtXCbnh8HbAIa85sgRXL/ZGFe0UpRPd3JaIuQFdEFsbeTon+IN4QudmdVCCHRNYP17HFWkp3LA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.623.0", + "@aws-sdk/client-sts": "3.623.0", + "@aws-sdk/core": "3.623.0", + "@aws-sdk/credential-provider-node": "3.623.0", + "@aws-sdk/middleware-endpoint-discovery": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", "tslib": "^2.6.2", "uuid": "^9.0.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" } }, "node_modules/@aws-sdk/client-lambda": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.535.0.tgz", - "integrity": "sha512-aR8/yblb3wEsPZ/U4XAx5k4Ek8+cEBzJIj6cFSa9OqBRHIWdu2f8bbQR7yDRYzq5Ta/nqFg2023gksee3KCSdw==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.535.0", - "@aws-sdk/core": "3.535.0", - "@aws-sdk/credential-provider-node": "3.535.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.535.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.535.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.0", - "@smithy/eventstream-serde-browser": "^2.2.0", - "@smithy/eventstream-serde-config-resolver": "^2.2.0", - "@smithy/eventstream-serde-node": "^2.2.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.2.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-stream": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "@smithy/util-waiter": "^2.2.0", + "version": "3.623.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.623.0.tgz", + "integrity": "sha512-hyPgz7quymu+sLQsJNUZa7YSDV0uSfk07XhnccDl0zwq7hf8BiqiEVB2z+y33m8zSFWn6ma3/8h2IsiIH5g0Vg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.623.0", + "@aws-sdk/client-sts": "3.623.0", + "@aws-sdk/core": "3.623.0", + "@aws-sdk/credential-provider-node": "3.623.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/eventstream-serde-browser": "^3.0.5", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.4", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-pinpoint": { + "version": "3.623.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-pinpoint/-/client-pinpoint-3.623.0.tgz", + "integrity": "sha512-rXumLfT+sxDInoVROI4oYihCbWWBCNCD1Vbu2QM/KPOo04cnzCK7A6HsDuq+CRU7S1A1hW2ILzkW4YEuXTnygA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.623.0", + "@aws-sdk/client-sts": "3.623.0", + "@aws-sdk/core": "3.623.0", + "@aws-sdk/credential-provider-node": "3.623.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.535.0.tgz", - "integrity": "sha512-h9eQRdFnjDRVBnPJIKXuX7D+isSAioIfZPC4PQwsL5BscTRlk4c90DX0R0uk64YUtp7LZu8TNtrosFZ/1HtTrQ==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.535.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.535.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.535.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.2.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", + "version": "3.623.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.623.0.tgz", + "integrity": "sha512-oEACriysQMnHIVcNp7TD6D1nzgiHfYK0tmMBMbUxgoFuCBkW9g9QYvspHN+S9KgoePfMEXHuPUe9mtG9AH9XeA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.623.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.535.0.tgz", - "integrity": "sha512-M2cG4EQXDpAJQyq33ORIr6abmdX9p9zX0ssVy8XwFNB7lrgoIKxuVoGL+fX+XMgecl24x7ELz6b4QlILOevbCw==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.535.0", - "@aws-sdk/core": "3.535.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.535.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.535.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.2.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", + "version": "3.623.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.623.0.tgz", + "integrity": "sha512-lMFEXCa6ES/FGV7hpyrppT1PiAkqQb51AbG0zVU3TIgI2IO4XX02uzMUXImRSRqRpGymRCbJCaCs9LtKvS/37Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.623.0", + "@aws-sdk/credential-provider-node": "3.623.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.535.0" + "@aws-sdk/client-sts": "^3.623.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.535.0.tgz", - "integrity": "sha512-ii9OOm3TJwP3JmO1IVJXKWIShVKPl0VtdlgROc/SkDglO/kuAw9eDdlROgc+qbFl+gm6bBTguOVTUXt3tS3flw==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.535.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.535.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.535.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.2.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", + "version": "3.623.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.623.0.tgz", + "integrity": "sha512-iJNdx76SOw0YjHAUv8aj3HXzSu3TKI7qSGuR+OGATwA/kpJZDd+4+WYBdGtr8YK+hPrGGqhfecuCkEg805O5iA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.623.0", + "@aws-sdk/core": "3.623.0", + "@aws-sdk/credential-provider-node": "3.623.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.535.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/core": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.535.0.tgz", - "integrity": "sha512-+Yusa9HziuaEDta1UaLEtMAtmgvxdxhPn7jgfRY6PplqAqgsfa5FR83sxy5qr2q7xjQTwHtV4MjQVuOjG9JsLw==", - "dependencies": { - "@smithy/core": "^1.4.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/signature-v4": "^2.2.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "fast-xml-parser": "4.2.5", + "version": "3.623.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.623.0.tgz", + "integrity": "sha512-8Toq3X6trX/67obSdh4K0MFQY4f132bEbr1i0YPDWk/O3KdBt12mLC/sW3aVRnlIs110XMuX9yrWWqJ8fDW10g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.3.2", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.535.0.tgz", - "integrity": "sha512-XppwO8c0GCGSAvdzyJOhbtktSEaShg14VJKg8mpMa1XcgqzmcqqHQjtDWbx5rZheY1VdpXZhpEzJkB6LpQejpA==", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/types": "^2.12.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", - "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/util-stream": "^2.2.0", + "version": "3.622.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.622.0.tgz", + "integrity": "sha512-VUHbr24Oll1RK3WR8XLUugLpgK9ZuxEm/NVeVqyFts1Ck9gsKpRg1x4eH7L7tW3SJ4TDEQNMbD7/7J+eoL2svg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.535.0.tgz", - "integrity": "sha512-bm3XOYlyCjtAb8eeHXLrxqRxYVRw2Iqv9IufdJb4gM13TbNSYniUT1WKaHxGIZ5p+FuNlXVhvk1OpHFM13+gXA==", - "dependencies": { - "@aws-sdk/client-sts": "3.535.0", - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.535.0", - "@aws-sdk/credential-provider-web-identity": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", + "version": "3.623.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.623.0.tgz", + "integrity": "sha512-kvXA1SwGneqGzFwRZNpESitnmaENHGFFuuTvgGwtMe7mzXWuA/LkXdbiHmdyAzOo0iByKTCD8uetuwh3CXy4Pw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.622.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.623.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.623.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.535.0.tgz", - "integrity": "sha512-6JXp/EuL6euUkH5k4d+lQFF6gBwukrcCOWfNHCmq14mNJf/cqT3HAX1VMtWFRSK20am0IxfYQGccb0/nZykdKg==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-http": "3.535.0", - "@aws-sdk/credential-provider-ini": "3.535.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.535.0", - "@aws-sdk/credential-provider-web-identity": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", + "version": "3.623.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.623.0.tgz", + "integrity": "sha512-qDwCOkhbu5PfaQHyuQ+h57HEx3+eFhKdtIw7aISziWkGdFrMe07yIBd7TJqGe4nxXnRF1pfkg05xeOlMId997g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.622.0", + "@aws-sdk/credential-provider-ini": "3.623.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.623.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.535.0.tgz", - "integrity": "sha512-9O1OaprGCnlb/kYl8RwmH7Mlg8JREZctB8r9sa1KhSsWFq/SWO0AuJTyowxD7zL5PkeS4eTvzFFHWCa3OO5epA==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.535.0.tgz", - "integrity": "sha512-2Dw0YIr8ETdFpq65CC4zK8ZIEbX78rXoNRZXUGNQW3oSKfL0tj8O8ErY6kg1IdEnYbGnEQ35q6luZ5GGNKLgDg==", - "dependencies": { - "@aws-sdk/client-sso": "3.535.0", - "@aws-sdk/token-providers": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", + "version": "3.623.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.623.0.tgz", + "integrity": "sha512-70LZhUb3l7cttEsg4A0S4Jq3qrCT/v5Jfyl8F7w1YZJt5zr3oPPcvDJxo/UYckFz4G4/5BhGa99jK8wMlNE9QA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.623.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.535.0.tgz", - "integrity": "sha512-t2/JWrKY0H66A7JW7CqX06/DG2YkJddikt5ymdQvx/Q7dRMJ3d+o/vgjoKr7RvEx/pNruCeyM1599HCvwrVMrg==", - "dependencies": { - "@aws-sdk/client-sts": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/types": "^2.12.0", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" } }, "node_modules/@aws-sdk/endpoint-cache": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.535.0.tgz", - "integrity": "sha512-sPG2l00iVuporK9AmPWq4UBcJURs2RN+vKA8QLRQANmQS3WFHWHamvGltxCjK79izkeqri882V4XlFpZfWhemA==", + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.572.0.tgz", + "integrity": "sha512-CzuRWMj/xtN9p9eP915nlPmlyniTzke732Ow/M60++gGgB3W+RtZyFftw3TEx+NzNhd1tH54dEcGiWdiNaBz3Q==", + "license": "Apache-2.0", "dependencies": { "mnemonist": "0.38.3", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/lib-dynamodb": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.535.0.tgz", - "integrity": "sha512-SJ3IcuN+BmwMv5ZlK04tCu/YhjqehdlYpQB+nK6h4Gvw7dfTTJua3p/OcWm5uDFvZvYi9SNAJFcepjZhxXdSNw==", + "version": "3.623.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.623.0.tgz", + "integrity": "sha512-NBDM4va0A2TthmwodOuptSwwEdZw2dn17s1QQkNGz+XbxYw8qPe3uomgiOEhPKznxIucqfdR9IZz6iBaEvzfyQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/util-dynamodb": "3.535.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", + "@aws-sdk/util-dynamodb": "3.623.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-dynamodb": "^3.0.0" + "@aws-sdk/client-dynamodb": "^3.623.0" } }, "node_modules/@aws-sdk/middleware-endpoint-discovery": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.535.0.tgz", - "integrity": "sha512-+EsqJB5A15RoTf0HxUdknF3hp+2WDg0HWc+QERUctzzYXy9l5LIQjmhQ96cWDyFttKmHE+4h6fjMZjJEeWOeYQ==", - "dependencies": { - "@aws-sdk/endpoint-cache": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/types": "^2.12.0", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.620.0.tgz", + "integrity": "sha512-T6kuydHBF4BPP5CVH53Fze7c2b9rqxWP88XrGtmNMXXdY4sXur1v/itGdS2l3gqRjxKo0LsmjmuQm9zL4vGneQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/endpoint-cache": "3.572.0", + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.535.0.tgz", - "integrity": "sha512-0h6TWjBWtDaYwHMQJI9ulafeS4lLaw1vIxRjbpH0svFRt6Eve+Sy8NlVhECfTU2hNz/fLubvrUxsXoThaLBIew==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/types": "^2.12.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.535.0.tgz", - "integrity": "sha512-huNHpONOrEDrdRTvSQr1cJiRMNf0S52NDXtaPzdxiubTkP+vni2MohmZANMOai/qT0olmEVX01LhZ0ZAOgmg6A==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/types": "^2.12.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.535.0.tgz", - "integrity": "sha512-am2qgGs+gwqmR4wHLWpzlZ8PWhm4ktj5bYSgDrsOfjhdBlWNxvPoID9/pDAz5RWL48+oH7I6SQzMqxXsFDikrw==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/types": "^2.12.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.535.0.tgz", - "integrity": "sha512-Uvb2WJ+zdHdCOtsWVPI/M0BcfNrjOYsicDZWtaljucRJKLclY5gNWwD+RwIC+8b5TvfnVOlH+N5jhvpi5Impog==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.535.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/types": "^2.12.0", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz", + "integrity": "sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.535.0.tgz", - "integrity": "sha512-IXOznDiaItBjsQy4Fil0kzX/J3HxIOknEphqHbOfUf+LpA5ugcsxuQQONrbEQusCBnfJyymrldBvBhFmtlU9Wg==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/types": "^2.12.0", - "@smithy/util-config-provider": "^2.3.0", - "@smithy/util-middleware": "^2.2.0", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.535.0.tgz", - "integrity": "sha512-4g+l/B9h1H/SiDtFRosW3pMwc+3PTXljZit+5NUBcET2XqcdUyHmgj3lBdu+CJ9CHdIMggRalYMAFXnRFe3Psg==", - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.614.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.535.0.tgz", - "integrity": "sha512-aY4MYfduNj+sRR37U7XxYR8wemfbKP6lx00ze2M2uubn7mZotuVrWYAafbMSXrdEMSToE5JDhr28vArSOoLcSg==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/util-dynamodb": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.535.0.tgz", - "integrity": "sha512-zHvmKRmAkJKFXzeTMifoooC2mS8jSRHBHSFxxscrZDgvZgo2g9ogIy52qz/3cWKI/ZQnrBSLhPpGYDZHrSgO8g==", + "version": "3.623.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.623.0.tgz", + "integrity": "sha512-uUfJ5gx9IWUeY89Q0Y+iImv7062gVoCg8KxBEONk57lP/MG5rmRjfSqirznJ00+hVDE4fzAitse84HbqLZOkag==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-dynamodb": "^3.0.0" + "@aws-sdk/client-dynamodb": "^3.623.0" } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.535.0.tgz", - "integrity": "sha512-c8TlaQsiPchOOmTTR6qvHCO2O7L7NJwlKWAoQJ2GqWDZuC5es/fyuF2rp1h+ZRrUVraUomS0YdGkAmaDC7hJQg==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz", + "integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/types": "^2.12.0", - "@smithy/util-endpoints": "^1.2.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.535.0.tgz", - "integrity": "sha512-PHJ3SL6d2jpcgbqdgiPxkXpu7Drc2PYViwxSIqvvMKhDwzSB1W3mMvtpzwKM4IE7zLFodZo0GKjJ9AsoXndXhA==", + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", + "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.535.0.tgz", - "integrity": "sha512-RWMcF/xV5n+nhaA/Ff5P3yNP3Kur/I+VNZngog4TEs92oB/nwOdAg/2JL8bVAhUbMrjTjpwm7PItziYFQoqyig==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/types": "^2.12.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.535.0.tgz", - "integrity": "sha512-dRek0zUuIT25wOWJlsRm97nTkUlh1NDcLsQZIN2Y8KxhwoXXWtJs5vaDPT+qAg+OpcNj80i1zLR/CirqlFg/TQ==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/types": "^2.12.0", + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -1546,123 +1477,47 @@ } } }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", - "dependencies": { - "tslib": "^2.3.1" - } - }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/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/@babel/compat-data": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, + "license": "MIT", "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/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, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/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, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/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 - }, - "node_modules/@babel/code-frame/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, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/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, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/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, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", - "dev": true, - "engines": { - "node": ">=6.9.0" + "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", - "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.0", - "@babel/parser": "^7.24.0", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", - "@babel/types": "^7.24.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -1678,14 +1533,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -1693,26 +1549,28 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -1725,6 +1583,7 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } @@ -1733,22 +1592,22 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.0.tgz", - "integrity": "sha512-QAH+vfvts51BCsNZ2PhY6HAggnlS6omLLFTsIpeqZk/MmJ6cW7tgz5yRv0fMJThcr6FmbMrENh1RgrWPTYA76g==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz", + "integrity": "sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/traverse": "^7.25.0", "semver": "^6.3.1" }, "engines": { @@ -1758,75 +1617,45 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -1836,35 +1665,38 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1874,91 +1706,88 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", - "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -1969,6 +1798,7 @@ "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": { "color-convert": "^1.9.0" }, @@ -1981,6 +1811,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -1995,6 +1826,7 @@ "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": { "color-name": "1.1.3" } @@ -2003,13 +1835,15 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@babel/highlight/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.8.0" } @@ -2019,6 +1853,7 @@ "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" } @@ -2028,6 +1863,7 @@ "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" }, @@ -2036,10 +1872,14 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", - "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.2" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -2053,6 +1893,7 @@ "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -2070,6 +1911,7 @@ "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", "dev": true, + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.20.5", "@babel/helper-compilation-targets": "^7.20.7", @@ -2089,6 +1931,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -2097,12 +1940,13 @@ } }, "node_modules/@babel/plugin-syntax-flow": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.23.3.tgz", - "integrity": "sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.7.tgz", + "integrity": "sha512-9G8GYT/dxn/D1IIKOUBmGX0mnmj46mGH9NnZyJLwtCpgh5f7D2VbuKodb+2s9m1Yavh1s7ASQN8lf0eqrb1LTw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2112,12 +1956,13 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", - "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2127,12 +1972,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2146,6 +1992,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -2154,12 +2001,13 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", - "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2169,12 +2017,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", - "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2184,12 +2033,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", - "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", + "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2199,18 +2049,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz", - "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-split-export-declaration": "^7.22.6", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.0.tgz", + "integrity": "sha512-xyi6qjr/fYU304fiRwFbekzkqVJZ6A7hOjWZd+89FVcBqPV3S9Wuozz82xdpLspckeaafntbzglaW4pqpzvtSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/traverse": "^7.25.0", "globals": "^11.1.0" }, "engines": { @@ -2221,13 +2070,14 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", - "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.15" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2237,12 +2087,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", - "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2252,13 +2103,14 @@ } }, "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz", - "integrity": "sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.25.2.tgz", + "integrity": "sha512-InBZ0O8tew5V0K6cHcQ+wgxlrjOw1W4wDXLkOTjLRD8GYhTSkxTVBtdy3MMtvYBrbAWa1Qm3hNoTc1620Yj+Mg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-flow": "^7.23.3" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/plugin-syntax-flow": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2268,13 +2120,14 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", - "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2284,14 +2137,15 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", - "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", + "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.1" }, "engines": { "node": ">=6.9.0" @@ -2301,12 +2155,13 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", - "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", + "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2316,12 +2171,13 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", - "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2331,14 +2187,15 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", - "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2348,13 +2205,14 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", - "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2364,12 +2222,13 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", - "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2379,12 +2238,13 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", - "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2394,12 +2254,13 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz", - "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", + "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2409,16 +2270,17 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", - "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz", + "integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/types": "^7.23.4" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/types": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -2428,12 +2290,13 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", - "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2443,13 +2306,14 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", - "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2459,12 +2323,13 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", - "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2474,10 +2339,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", - "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", "dev": true, + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2486,33 +2352,32 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", - "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2521,13 +2386,14 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2538,13 +2404,34 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/@cfaester/enzyme-adapter-react-18": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cfaester/enzyme-adapter-react-18/-/enzyme-adapter-react-18-0.8.0.tgz", + "integrity": "sha512-3Z3ThTUouHwz8oIyhTYQljEMNRFtlVyc3VOOHCbxs47U6cnXs8K9ygi/c1tv49s7MBlTXeIcuN+Ttd9aPtILFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "enzyme-shallow-equal": "^1.0.0", + "function.prototype.name": "^1.1.6", + "has": "^1.0.4", + "react-is": "^18.2.0", + "react-shallow-renderer": "^16.15.0" + }, + "peerDependencies": { + "enzyme": "^3.11.0", + "react": ">=18", + "react-dom": ">=18" + } }, "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": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -2557,19 +2444,45 @@ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -2579,13 +2492,14 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -2595,13 +2509,14 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -2611,13 +2526,14 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -2627,13 +2543,14 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2643,13 +2560,14 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2659,13 +2577,14 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2675,13 +2594,14 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2691,13 +2611,14 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2707,13 +2628,14 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2723,13 +2645,14 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2739,13 +2662,14 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2755,13 +2679,14 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2771,13 +2696,14 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2787,13 +2713,14 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2803,13 +2730,14 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2819,13 +2747,14 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2835,13 +2764,14 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -2851,13 +2781,14 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -2867,13 +2798,14 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -2883,13 +2815,14 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2899,13 +2832,14 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2915,13 +2849,14 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2935,6 +2870,7 @@ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -2946,10 +2882,11 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -2959,6 +2896,7 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -2982,6 +2920,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2992,6 +2931,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -3007,6 +2947,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3019,6 +2960,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -3031,24 +2973,34 @@ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@exodus/schemasafe": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", + "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", + "dev": true, + "license": "MIT" + }, "node_modules/@fastify/busboy": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" } }, "node_modules/@graphql-codegen/add": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-5.0.2.tgz", - "integrity": "sha512-ouBkSvMFUhda5VoKumo/ZvsZM9P5ZTyDsI8LW18VxSNWOjrTeLXBWHG8Gfaai0HwhflPtCYVABbriEcOmrRShQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-5.0.3.tgz", + "integrity": "sha512-SxXPmramkth8XtBlAHu4H4jYcYXM/o3p01+psU+0NADQowA8jtYkK6MW5rV6T+CxkEaNZItfSmZRPgIuypcqnA==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.3", "tslib": "~2.6.0" @@ -3062,6 +3014,7 @@ "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-5.0.2.tgz", "integrity": "sha512-MBIaFqDiLKuO4ojN6xxG9/xL9wmfD3ZjZ7RsPjwQnSHBCUXnEkdKvX+JVpx87Pq29Ycn8wTJUguXnTZ7Di0Mlw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/generator": "^7.18.13", "@babel/template": "^7.18.10", @@ -3120,6 +3073,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3132,20 +3086,21 @@ } }, "node_modules/@graphql-codegen/client-preset": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-4.2.4.tgz", - "integrity": "sha512-k1c8v2YxJhhITGQGxViG9asLAoop9m7X9duU7Zztqjc98ooxsUzXICfvAWsH3mLAUibXAx4Ax6BPzKsTtQmBPg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-4.3.3.tgz", + "integrity": "sha512-IrDsSVe8bkKtxgVfKPHzjL9tYlv7KEpA59R4gZLqx/t2WIJncW1i0OMvoz9tgoZsFEs8OKKgXZbnwPZ/Qf1kEw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/template": "^7.20.7", - "@graphql-codegen/add": "^5.0.2", - "@graphql-codegen/gql-tag-operations": "4.0.6", - "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-codegen/typed-document-node": "^5.0.6", - "@graphql-codegen/typescript": "^4.0.6", - "@graphql-codegen/typescript-operations": "^4.2.0", - "@graphql-codegen/visitor-plugin-common": "^5.1.0", + "@graphql-codegen/add": "^5.0.3", + "@graphql-codegen/gql-tag-operations": "4.0.9", + "@graphql-codegen/plugin-helpers": "^5.0.4", + "@graphql-codegen/typed-document-node": "^5.0.9", + "@graphql-codegen/typescript": "^4.0.9", + "@graphql-codegen/typescript-operations": "^4.2.3", + "@graphql-codegen/visitor-plugin-common": "^5.3.1", "@graphql-tools/documents": "^1.0.0", "@graphql-tools/utils": "^10.0.0", "@graphql-typed-document-node/core": "3.2.0", @@ -3160,6 +3115,7 @@ "resolved": "https://registry.npmjs.org/@graphql-codegen/core/-/core-4.0.2.tgz", "integrity": "sha512-IZbpkhwVqgizcjNiaVzNAzm/xbWT6YnGgeOLwVjm4KbJn3V2jchVtuzHH09G5/WkkLSk2wgbXNdwjM41JxO6Eg==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.3", "@graphql-tools/schema": "^10.0.0", @@ -3171,13 +3127,14 @@ } }, "node_modules/@graphql-codegen/gql-tag-operations": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-4.0.6.tgz", - "integrity": "sha512-y6iXEDpDNjwNxJw3WZqX1/Znj0QHW7+y8O+t2V8qvbTT+3kb2lr9ntc8By7vCr6ctw9tXI4XKaJgpTstJDOwFA==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-4.0.9.tgz", + "integrity": "sha512-lVgu1HClel896HqZAEjynatlU6eJrYOw+rh05DPgM150xvmb7Gz5TnRHA2vfwlDNIXDaToAIpz5RFfkjjnYM1Q==", "dev": true, + "license": "MIT", "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-codegen/visitor-plugin-common": "5.1.0", + "@graphql-codegen/plugin-helpers": "^5.0.4", + "@graphql-codegen/visitor-plugin-common": "5.3.1", "@graphql-tools/utils": "^10.0.0", "auto-bind": "~4.0.0", "tslib": "~2.6.0" @@ -3191,6 +3148,7 @@ "resolved": "https://registry.npmjs.org/@graphql-codegen/introspection/-/introspection-4.0.3.tgz", "integrity": "sha512-4cHRG15Zu4MXMF4wTQmywNf4+fkDYv5lTbzraVfliDnB8rJKcaurQpRBi11KVuQUe24YTq/Cfk4uwewfNikWoA==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.3", "@graphql-codegen/visitor-plugin-common": "^5.0.0", @@ -3201,10 +3159,11 @@ } }, "node_modules/@graphql-codegen/plugin-helpers": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-5.0.3.tgz", - "integrity": "sha512-yZ1rpULIWKBZqCDlvGIJRSyj1B2utkEdGmXZTBT/GVayP4hyRYlkd36AJV/LfEsVD8dnsKL5rLz2VTYmRNlJ5Q==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-5.0.4.tgz", + "integrity": "sha512-MOIuHFNWUnFnqVmiXtrI+4UziMTYrcquljaI5f/T/Bc7oO7sXcfkAvgkNWEEi9xWreYwvuer3VHCuPI/lAFWbw==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-tools/utils": "^10.0.0", "change-case-all": "1.0.15", @@ -3218,10 +3177,11 @@ } }, "node_modules/@graphql-codegen/schema-ast": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/schema-ast/-/schema-ast-4.0.2.tgz", - "integrity": "sha512-5mVAOQQK3Oz7EtMl/l3vOQdc2aYClUzVDHHkMvZlunc+KlGgl81j8TLa+X7ANIllqU4fUEsQU3lJmk4hXP6K7Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/schema-ast/-/schema-ast-4.1.0.tgz", + "integrity": "sha512-kZVn0z+th9SvqxfKYgztA6PM7mhnSZaj4fiuBWvMTqA+QqQ9BBed6Pz41KuD/jr0gJtnlr2A4++/0VlpVbCTmQ==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.3", "@graphql-tools/utils": "^10.0.0", @@ -3232,13 +3192,14 @@ } }, "node_modules/@graphql-codegen/typed-document-node": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-5.0.6.tgz", - "integrity": "sha512-US0J95hOE2/W/h42w4oiY+DFKG7IetEN1mQMgXXeat1w6FAR5PlIz4JrRrEkiVfVetZ1g7K78SOwBD8/IJnDiA==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-5.0.9.tgz", + "integrity": "sha512-Wx6fyA4vpfIbfNTMiWUECGnjqzKkJdEbZHxVMIegiCBPzBYPAJV4mZZcildLAfm2FtZcgW4YKtFoTbnbXqPB3w==", "dev": true, + "license": "MIT", "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-codegen/visitor-plugin-common": "5.1.0", + "@graphql-codegen/plugin-helpers": "^5.0.4", + "@graphql-codegen/visitor-plugin-common": "5.3.1", "auto-bind": "~4.0.0", "change-case-all": "1.0.15", "tslib": "~2.6.0" @@ -3248,14 +3209,15 @@ } }, "node_modules/@graphql-codegen/typescript": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-4.0.6.tgz", - "integrity": "sha512-IBG4N+Blv7KAL27bseruIoLTjORFCT3r+QYyMC3g11uY3/9TPpaUyjSdF70yBe5GIQ6dAgDU+ENUC1v7EPi0rw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-4.0.9.tgz", + "integrity": "sha512-0O35DMR4d/ctuHL1Zo6mRUUzp0BoszKfeWsa6sCm/g70+S98+hEfTwZNDkQHylLxapiyjssF9uw/F+sXqejqLw==", "dev": true, + "license": "MIT", "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.3", + "@graphql-codegen/plugin-helpers": "^5.0.4", "@graphql-codegen/schema-ast": "^4.0.2", - "@graphql-codegen/visitor-plugin-common": "5.1.0", + "@graphql-codegen/visitor-plugin-common": "5.3.1", "auto-bind": "~4.0.0", "tslib": "~2.6.0" }, @@ -3264,14 +3226,15 @@ } }, "node_modules/@graphql-codegen/typescript-operations": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-4.2.0.tgz", - "integrity": "sha512-lmuwYb03XC7LNRS8oo9M4/vlOrq/wOKmTLBHlltK2YJ1BO/4K/Q9Jdv/jDmJpNydHVR1fmeF4wAfsIp1f9JibA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-4.2.3.tgz", + "integrity": "sha512-6z7avSSOr03l5SyKbeDs7MzRyGwnQFSCqQm8Om5wIuoIgXVu2gXRmcJAY/I7SLdAy9xbF4Sho7XNqieFM2CAFQ==", "dev": true, + "license": "MIT", "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-codegen/typescript": "^4.0.6", - "@graphql-codegen/visitor-plugin-common": "5.1.0", + "@graphql-codegen/plugin-helpers": "^5.0.4", + "@graphql-codegen/typescript": "^4.0.9", + "@graphql-codegen/visitor-plugin-common": "5.3.1", "auto-bind": "~4.0.0", "tslib": "~2.6.0" }, @@ -3280,14 +3243,15 @@ } }, "node_modules/@graphql-codegen/typescript-resolvers": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-4.0.6.tgz", - "integrity": "sha512-7OBFzZ2xSkYgMgcc1A3xNqbBHHSQXBesLrG86Sh+Jj0PQQB3Om8j1HSFs64PD/l5Kri2dXgm3oim/89l3Rl3lw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-4.2.1.tgz", + "integrity": "sha512-q/ggqNSKNGG9bn49DdZrw2KokagDZmzl1EpxIfzmpHrPa3XaCLfxQuNNEUhqEXtJzQZtLfuYvGy1y+MrTU8WnA==", "dev": true, + "license": "MIT", "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-codegen/typescript": "^4.0.6", - "@graphql-codegen/visitor-plugin-common": "5.1.0", + "@graphql-codegen/plugin-helpers": "^5.0.4", + "@graphql-codegen/typescript": "^4.0.9", + "@graphql-codegen/visitor-plugin-common": "5.3.1", "@graphql-tools/utils": "^10.0.0", "auto-bind": "~4.0.0", "tslib": "~2.6.0" @@ -3297,12 +3261,13 @@ } }, "node_modules/@graphql-codegen/visitor-plugin-common": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.1.0.tgz", - "integrity": "sha512-eamQxtA9bjJqI2lU5eYoA1GbdMIRT2X8m8vhWYsVQVWD3qM7sx/IqJU0kx0J3Vd4/CSd36BzL6RKwksibytDIg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.3.1.tgz", + "integrity": "sha512-MktoBdNZhSmugiDjmFl1z6rEUUaqyxtFJYWnDilE7onkPgyw//O0M+TuPBJPBWdyV6J2ond0Hdqtq+rkghgSIQ==", "dev": true, + "license": "MIT", "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.3", + "@graphql-codegen/plugin-helpers": "^5.0.4", "@graphql-tools/optimize": "^2.0.0", "@graphql-tools/relay-operation-optimizer": "^7.0.0", "@graphql-tools/utils": "^10.0.0", @@ -3322,6 +3287,7 @@ "resolved": "https://registry.npmjs.org/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-8.0.1.tgz", "integrity": "sha512-NaPeVjtrfbPXcl+MLQCJLWtqe2/E4bbAqcauEOQ+3sizw1Fc2CNmhHRF8a6W4D0ekvTRRXAMptXYgA2uConbrA==", "dev": true, + "license": "MIT", "dependencies": { "@ardatan/sync-fetch": "^0.0.1", "@graphql-tools/utils": "^10.0.13", @@ -3335,22 +3301,14 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/events": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", - "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", - "dev": true, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/fetch": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", - "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.19.tgz", + "integrity": "sha512-J+zopRcUVOhkiQYlHpxOEZuOgZtqW9xMaNQFDjESm9vRcyATms+E2/p2mZiVQGllPqWflkA3SzoJC1MxV4Pf9g==", "dev": true, + "license": "MIT", "dependencies": { - "@whatwg-node/node-fetch": "^0.5.7", + "@whatwg-node/node-fetch": "^0.5.16", "urlpattern-polyfill": "^10.0.0" }, "engines": { @@ -3358,32 +3316,34 @@ } }, "node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.8.tgz", - "integrity": "sha512-rB+2P3oi9fD4TcsijkflJAQqOh4yZrPgOV4fGaDgCdOqqwTicJvL2nnVbr3comW8bxEuypOcyE1AtBtkpip0Gw==", + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.20.tgz", + "integrity": "sha512-DFLsOG//CrDdIO0x7Q7Ompxj3TZhB4iMDeXpQKY4toSbIbzsKmbwyOkzXMwvV1syxvAtPoHBzyGGtDrPV424FA==", "dev": true, + "license": "MIT", "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", - "@whatwg-node/events": "^0.1.0", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "tslib": "^2.6.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@graphql-tools/apollo-engine-loader/node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@graphql-tools/batch-execute": { "version": "9.0.4", "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-9.0.4.tgz", "integrity": "sha512-kkebDLXgDrep5Y0gK1RN3DMUlLqNhg60OAz0lTCqrYeja6DshxLtLkj+zV4mVbBA4mQOEoBmw6g1LZs3dA84/w==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-tools/utils": "^10.0.13", "dataloader": "^2.2.2", @@ -3398,12 +3358,13 @@ } }, "node_modules/@graphql-tools/code-file-loader": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-8.1.1.tgz", - "integrity": "sha512-q4KN25EPSUztc8rA8YUU3ufh721Yk12xXDbtUA+YstczWS7a1RJlghYMFEfR1HsHSYbF7cUqkbnTKSGM3o52bQ==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-8.1.3.tgz", + "integrity": "sha512-Qoo8VyU0ux7k20DkzL5wFm7Y6iqlG1GQ0xA4T3EQbm4B/qbENsMc38l76QnXYIVmIlKAnD9EAvzxPEQ8iv+ZPA==", "dev": true, + "license": "MIT", "dependencies": { - "@graphql-tools/graphql-tag-pluck": "8.3.0", + "@graphql-tools/graphql-tag-pluck": "8.3.2", "@graphql-tools/utils": "^10.0.13", "globby": "^11.0.3", "tslib": "^2.4.0", @@ -3417,15 +3378,16 @@ } }, "node_modules/@graphql-tools/delegate": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-10.0.4.tgz", - "integrity": "sha512-WswZRbQZMh/ebhc8zSomK9DIh6Pd5KbuiMsyiKkKz37TWTrlCOe+4C/fyrBFez30ksq6oFyCeSKMwfrCbeGo0Q==", + "version": "10.0.17", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-10.0.17.tgz", + "integrity": "sha512-YIJleGaSjYnqIcJ5uoBWVBBE3eP5h3CvEM9PiANHtRUBmoNBKdYstkrS3IqBSlgKLsboD5CTYfmXDVQAPfH+mw==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-tools/batch-execute": "^9.0.4", - "@graphql-tools/executor": "^1.2.1", - "@graphql-tools/schema": "^10.0.3", - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/executor": "^1.3.0", + "@graphql-tools/schema": "^10.0.4", + "@graphql-tools/utils": "^10.2.3", "dataloader": "^2.2.2", "tslib": "^2.5.0" }, @@ -3437,10 +3399,11 @@ } }, "node_modules/@graphql-tools/documents": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/documents/-/documents-1.0.0.tgz", - "integrity": "sha512-rHGjX1vg/nZ2DKqRGfDPNC55CWZBMldEVcH+91BThRa6JeT80NqXknffLLEZLRUxyikCfkwMsk6xR3UNMqG0Rg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/documents/-/documents-1.0.1.tgz", + "integrity": "sha512-aweoMH15wNJ8g7b2r4C4WRuJxZ0ca8HtNO54rkye/3duxTkW4fGBEutCx03jCIr5+a1l+4vFJNP859QnAVBVCA==", "dev": true, + "license": "MIT", "dependencies": { "lodash.sortby": "^4.7.0", "tslib": "^2.4.0" @@ -3453,12 +3416,13 @@ } }, "node_modules/@graphql-tools/executor": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.2.2.tgz", - "integrity": "sha512-wZkyjndwlzi01HTU3PDveoucKA8qVO0hdKmJhjIGK/vRN/A4w5rDdeqRGcyXVss0clCAy3R6jpixCVu5pWs2Qg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.3.0.tgz", + "integrity": "sha512-e+rmEf/2EO4hDnbkO8mTS2FI+jGUNmYkSDKw5TgPVlO8VOKS+TXmJBK6E9v4Gc/39yVkZsffYfW/R8obJrA0mg==", "dev": true, + "license": "MIT", "dependencies": { - "@graphql-tools/utils": "^10.1.1", + "@graphql-tools/utils": "^10.2.3", "@graphql-typed-document-node/core": "3.2.0", "@repeaterjs/repeater": "^3.0.4", "tslib": "^2.4.0", @@ -3472,17 +3436,18 @@ } }, "node_modules/@graphql-tools/executor-graphql-ws": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-1.1.2.tgz", - "integrity": "sha512-+9ZK0rychTH1LUv4iZqJ4ESbmULJMTsv3XlFooPUngpxZkk00q6LqHKJRrsLErmQrVaC7cwQCaRBJa0teK17Lg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-1.2.0.tgz", + "integrity": "sha512-tSYC1QdrabWexLrYV0UI3uRGbde9WCY/bRhq6Jc+VXMZcfq6ea6pP5NEAVTfwbhUQ4xZvJABVVbKXtKb9uTg1w==", "dev": true, + "license": "MIT", "dependencies": { - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/utils": "^10.3.0", "@types/ws": "^8.0.0", "graphql-ws": "^5.14.0", "isomorphic-ws": "^5.0.0", "tslib": "^2.4.0", - "ws": "^8.13.0" + "ws": "^8.17.1" }, "engines": { "node": ">=16.0.0" @@ -3492,12 +3457,13 @@ } }, "node_modules/@graphql-tools/executor-http": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor-http/-/executor-http-1.0.9.tgz", - "integrity": "sha512-+NXaZd2MWbbrWHqU4EhXcrDbogeiCDmEbrAN+rMn4Nu2okDjn2MTFDbTIab87oEubQCH4Te1wDkWPKrzXup7+Q==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-http/-/executor-http-1.1.5.tgz", + "integrity": "sha512-ZAsVGUwafPc1GapLA1yoJuRx7ihpVdAv7JDHmlI2eHRQsJnMVQwcxHnjfUb/id9YAEBrP86/s4pgEoRyad3Zng==", "dev": true, + "license": "MIT", "dependencies": { - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/utils": "^10.3.2", "@repeaterjs/repeater": "^3.0.4", "@whatwg-node/fetch": "^0.9.0", "extract-files": "^11.0.0", @@ -3512,22 +3478,14 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/events": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", - "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", - "dev": true, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/fetch": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", - "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.19.tgz", + "integrity": "sha512-J+zopRcUVOhkiQYlHpxOEZuOgZtqW9xMaNQFDjESm9vRcyATms+E2/p2mZiVQGllPqWflkA3SzoJC1MxV4Pf9g==", "dev": true, + "license": "MIT", "dependencies": { - "@whatwg-node/node-fetch": "^0.5.7", + "@whatwg-node/node-fetch": "^0.5.16", "urlpattern-polyfill": "^10.0.0" }, "engines": { @@ -3535,38 +3493,40 @@ } }, "node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.8.tgz", - "integrity": "sha512-rB+2P3oi9fD4TcsijkflJAQqOh4yZrPgOV4fGaDgCdOqqwTicJvL2nnVbr3comW8bxEuypOcyE1AtBtkpip0Gw==", + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.20.tgz", + "integrity": "sha512-DFLsOG//CrDdIO0x7Q7Ompxj3TZhB4iMDeXpQKY4toSbIbzsKmbwyOkzXMwvV1syxvAtPoHBzyGGtDrPV424FA==", "dev": true, + "license": "MIT", "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", - "@whatwg-node/events": "^0.1.0", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "tslib": "^2.6.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@graphql-tools/executor-http/node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@graphql-tools/executor-legacy-ws": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor-legacy-ws/-/executor-legacy-ws-1.0.6.tgz", - "integrity": "sha512-lDSxz9VyyquOrvSuCCnld3256Hmd+QI2lkmkEv7d4mdzkxkK4ddAWW1geQiWrQvWmdsmcnGGlZ7gDGbhEExwqg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-legacy-ws/-/executor-legacy-ws-1.1.0.tgz", + "integrity": "sha512-k+6ZyiaAd8SmwuzbEOfA/LVkuI1nqidhoMw+CJ7c41QGOjSMzc0VS0UZbJyeitI0n7a+uP/Meln1wjzJ2ReDtQ==", "dev": true, + "license": "MIT", "dependencies": { - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/utils": "^10.3.0", "@types/ws": "^8.0.0", "isomorphic-ws": "^5.0.0", "tslib": "^2.4.0", - "ws": "^8.15.0" + "ws": "^8.17.1" }, "engines": { "node": ">=16.0.0" @@ -3576,12 +3536,13 @@ } }, "node_modules/@graphql-tools/git-loader": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-8.0.5.tgz", - "integrity": "sha512-P97/1mhruDiA6D5WUmx3n/aeGPLWj2+4dpzDOxFGGU+z9NcI/JdygMkeFpGZNHeJfw+kHfxgPcMPnxHcyhAoVA==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-8.0.7.tgz", + "integrity": "sha512-+s23lxHR24+zLDk9/Hfl7/8Qcal8Q1yJ8armRp1fvcJyuc0RTZv97ZoZb0tArTfME74z+kJ92Mx4SfZMd7mHSQ==", "dev": true, + "license": "MIT", "dependencies": { - "@graphql-tools/graphql-tag-pluck": "8.3.0", + "@graphql-tools/graphql-tag-pluck": "8.3.2", "@graphql-tools/utils": "^10.0.13", "is-glob": "4.0.3", "micromatch": "^4.0.4", @@ -3600,6 +3561,7 @@ "resolved": "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-8.0.1.tgz", "integrity": "sha512-W4dFLQJ5GtKGltvh/u1apWRFKBQOsDzFxO9cJkOYZj1VzHCpRF43uLST4VbCfWve+AwBqOuKr7YgkHoxpRMkcg==", "dev": true, + "license": "MIT", "dependencies": { "@ardatan/sync-fetch": "^0.0.1", "@graphql-tools/executor-http": "^1.0.9", @@ -3616,22 +3578,14 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/events": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", - "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", - "dev": true, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/fetch": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", - "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.19.tgz", + "integrity": "sha512-J+zopRcUVOhkiQYlHpxOEZuOgZtqW9xMaNQFDjESm9vRcyATms+E2/p2mZiVQGllPqWflkA3SzoJC1MxV4Pf9g==", "dev": true, + "license": "MIT", "dependencies": { - "@whatwg-node/node-fetch": "^0.5.7", + "@whatwg-node/node-fetch": "^0.5.16", "urlpattern-polyfill": "^10.0.0" }, "engines": { @@ -3639,32 +3593,34 @@ } }, "node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.8.tgz", - "integrity": "sha512-rB+2P3oi9fD4TcsijkflJAQqOh4yZrPgOV4fGaDgCdOqqwTicJvL2nnVbr3comW8bxEuypOcyE1AtBtkpip0Gw==", + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.20.tgz", + "integrity": "sha512-DFLsOG//CrDdIO0x7Q7Ompxj3TZhB4iMDeXpQKY4toSbIbzsKmbwyOkzXMwvV1syxvAtPoHBzyGGtDrPV424FA==", "dev": true, + "license": "MIT", "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", - "@whatwg-node/events": "^0.1.0", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "tslib": "^2.6.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@graphql-tools/github-loader/node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@graphql-tools/graphql-file-loader": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-8.0.1.tgz", "integrity": "sha512-7gswMqWBabTSmqbaNyWSmRRpStWlcCkBc73E6NZNlh4YNuiyKOwbvSkOUYFOqFMfEL+cFsXgAvr87Vz4XrYSbA==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-tools/import": "7.0.1", "@graphql-tools/utils": "^10.0.13", @@ -3680,10 +3636,11 @@ } }, "node_modules/@graphql-tools/graphql-tag-pluck": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.0.tgz", - "integrity": "sha512-gNqukC+s7iHC7vQZmx1SEJQmLnOguBq+aqE2zV2+o1hxkExvKqyFli1SY/9gmukFIKpKutCIj+8yLOM+jARutw==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.2.tgz", + "integrity": "sha512-wJKkDjXRg2qJAVhAVE96zJGMli8Ity9mKUB7gTbvJwsAniaquRqLcTXUQ19X9qVT4ACzbbp+tAfk96b2U3tfog==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.22.9", "@babel/parser": "^7.16.8", @@ -3705,6 +3662,7 @@ "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-7.0.1.tgz", "integrity": "sha512-935uAjAS8UAeXThqHfYVr4HEAp6nHJ2sximZKO1RzUTq5WoALMAhhGARl0+ecm6X+cqNUwIChJbjtaa6P/ML0w==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-tools/utils": "^10.0.13", "resolve-from": "5.0.0", @@ -3722,6 +3680,7 @@ "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-8.0.1.tgz", "integrity": "sha512-lAy2VqxDAHjVyqeJonCP6TUemrpYdDuKt25a10X6zY2Yn3iFYGnuIDQ64cv3ytyGY6KPyPB+Kp+ZfOkNDG3FQA==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-tools/utils": "^10.0.13", "globby": "^11.0.3", @@ -3740,6 +3699,7 @@ "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-8.0.2.tgz", "integrity": "sha512-S+E/cmyVmJ3CuCNfDuNF2EyovTwdWfQScXv/2gmvJOti2rGD8jTt9GYVzXaxhblLivQR9sBUCNZu/w7j7aXUCA==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-tools/schema": "^10.0.3", "@graphql-tools/utils": "^10.0.13", @@ -3754,9 +3714,10 @@ } }, "node_modules/@graphql-tools/merge": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.3.tgz", - "integrity": "sha512-FeKv9lKLMwqDu0pQjPpF59GY3HReUkWXKsMIuMuJQOKh9BETu7zPEFUELvcw8w+lwZkl4ileJsHXC9+AnsT2Lw==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.4.tgz", + "integrity": "sha512-MivbDLUQ+4Q8G/Hp/9V72hbn810IJDEZQ57F01sHnlrrijyadibfVhaQfW/pNH+9T/l8ySZpaR/DpL5i+ruZ+g==", + "license": "MIT", "dependencies": { "@graphql-tools/utils": "^10.0.13", "tslib": "^2.4.0" @@ -3769,13 +3730,14 @@ } }, "node_modules/@graphql-tools/mock": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-9.0.2.tgz", - "integrity": "sha512-xt8EdprEBwiUrBGAiLZ5gARB0JAr4xGPypY1qzqvpSezedzqvUeKwztCt7Oe6WddNRwUBff26Dabnd3hmrhQ2g==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-9.0.4.tgz", + "integrity": "sha512-/pfrBoL6QkKBrJcaOZ/2FtfoAYmiQE+L6Up3hWbSWjg1XJfecjqptLTcM2Wf0dmHAFkjCK1k4QVNHMmGJ94IOA==", "dev": true, + "license": "MIT", "dependencies": { - "@graphql-tools/schema": "^10.0.3", - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/schema": "^10.0.4", + "@graphql-tools/utils": "^10.2.1", "fast-json-stable-stringify": "^2.1.0", "tslib": "^2.4.0" }, @@ -3791,6 +3753,7 @@ "resolved": "https://registry.npmjs.org/@graphql-tools/optimize/-/optimize-2.0.0.tgz", "integrity": "sha512-nhdT+CRGDZ+bk68ic+Jw1OZ99YCDIKYA5AlVAnBHJvMawSx9YQqQAIj4refNc1/LRieGiuWvhbG3jvPVYho0Dg==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.4.0" }, @@ -3802,15 +3765,15 @@ } }, "node_modules/@graphql-tools/prisma-loader": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/prisma-loader/-/prisma-loader-8.0.3.tgz", - "integrity": "sha512-oZhxnMr3Jw2WAW1h9FIhF27xWzIB7bXWM8olz4W12oII4NiZl7VRkFw9IT50zME2Bqi9LGh9pkmMWkjvbOpl+Q==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/prisma-loader/-/prisma-loader-8.0.4.tgz", + "integrity": "sha512-hqKPlw8bOu/GRqtYr0+dINAI13HinTVYBDqhwGAPIFmLr5s+qKskzgCiwbsckdrb5LWVFmVZc+UXn80OGiyBzg==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-tools/url-loader": "^8.0.2", "@graphql-tools/utils": "^10.0.13", "@types/js-yaml": "^4.0.0", - "@types/json-stable-stringify": "^1.0.32", "@whatwg-node/fetch": "^0.9.0", "chalk": "^4.1.0", "debug": "^4.3.1", @@ -3820,7 +3783,6 @@ "https-proxy-agent": "^7.0.0", "jose": "^5.0.0", "js-yaml": "^4.0.0", - "json-stable-stringify": "^1.0.1", "lodash": "^4.17.20", "scuid": "^1.1.0", "tslib": "^2.4.0", @@ -3833,22 +3795,14 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/events": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", - "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", - "dev": true, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/fetch": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", - "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.19.tgz", + "integrity": "sha512-J+zopRcUVOhkiQYlHpxOEZuOgZtqW9xMaNQFDjESm9vRcyATms+E2/p2mZiVQGllPqWflkA3SzoJC1MxV4Pf9g==", "dev": true, + "license": "MIT", "dependencies": { - "@whatwg-node/node-fetch": "^0.5.7", + "@whatwg-node/node-fetch": "^0.5.16", "urlpattern-polyfill": "^10.0.0" }, "engines": { @@ -3856,19 +3810,19 @@ } }, "node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.8.tgz", - "integrity": "sha512-rB+2P3oi9fD4TcsijkflJAQqOh4yZrPgOV4fGaDgCdOqqwTicJvL2nnVbr3comW8bxEuypOcyE1AtBtkpip0Gw==", + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.20.tgz", + "integrity": "sha512-DFLsOG//CrDdIO0x7Q7Ompxj3TZhB4iMDeXpQKY4toSbIbzsKmbwyOkzXMwvV1syxvAtPoHBzyGGtDrPV424FA==", "dev": true, + "license": "MIT", "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", - "@whatwg-node/events": "^0.1.0", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "tslib": "^2.6.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@graphql-tools/prisma-loader/node_modules/chalk": { @@ -3876,6 +3830,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3891,13 +3846,15 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@graphql-tools/relay-operation-optimizer": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.1.tgz", "integrity": "sha512-y0ZrQ/iyqWZlsS/xrJfSir3TbVYJTYmMOu4TaSz6F4FRDTQ3ie43BlKkhf04rC28pnUOS4BO9pDcAo1D30l5+A==", "dev": true, + "license": "MIT", "dependencies": { "@ardatan/relay-compiler": "12.0.0", "@graphql-tools/utils": "^10.0.13", @@ -3911,12 +3868,13 @@ } }, "node_modules/@graphql-tools/schema": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.3.tgz", - "integrity": "sha512-p28Oh9EcOna6i0yLaCFOnkcBDQECVf3SCexT6ktb86QNj9idnkhI+tCxnwZDh58Qvjd2nURdkbevvoZkvxzCog==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.4.tgz", + "integrity": "sha512-HuIwqbKxPaJujox25Ra4qwz0uQzlpsaBOzO6CVfzB/MemZdd+Gib8AIvfhQArK0YIN40aDran/yi+E5Xf0mQww==", + "license": "MIT", "dependencies": { "@graphql-tools/merge": "^9.0.3", - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/utils": "^10.2.1", "tslib": "^2.4.0", "value-or-promise": "^1.0.12" }, @@ -3932,6 +3890,7 @@ "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-8.0.2.tgz", "integrity": "sha512-1dKp2K8UuFn7DFo1qX5c1cyazQv2h2ICwA9esHblEqCYrgf69Nk8N7SODmsfWg94OEaI74IqMoM12t7eIGwFzQ==", "dev": true, + "license": "MIT", "dependencies": { "@ardatan/sync-fetch": "^0.0.1", "@graphql-tools/delegate": "^10.0.4", @@ -3954,22 +3913,14 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/events": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", - "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", - "dev": true, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/fetch": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", - "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.19.tgz", + "integrity": "sha512-J+zopRcUVOhkiQYlHpxOEZuOgZtqW9xMaNQFDjESm9vRcyATms+E2/p2mZiVQGllPqWflkA3SzoJC1MxV4Pf9g==", "dev": true, + "license": "MIT", "dependencies": { - "@whatwg-node/node-fetch": "^0.5.7", + "@whatwg-node/node-fetch": "^0.5.16", "urlpattern-polyfill": "^10.0.0" }, "engines": { @@ -3977,31 +3928,33 @@ } }, "node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.8.tgz", - "integrity": "sha512-rB+2P3oi9fD4TcsijkflJAQqOh4yZrPgOV4fGaDgCdOqqwTicJvL2nnVbr3comW8bxEuypOcyE1AtBtkpip0Gw==", + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.20.tgz", + "integrity": "sha512-DFLsOG//CrDdIO0x7Q7Ompxj3TZhB4iMDeXpQKY4toSbIbzsKmbwyOkzXMwvV1syxvAtPoHBzyGGtDrPV424FA==", "dev": true, + "license": "MIT", "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", - "@whatwg-node/events": "^0.1.0", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "tslib": "^2.6.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@graphql-tools/url-loader/node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@graphql-tools/utils": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.1.2.tgz", - "integrity": "sha512-fX13CYsDnX4yifIyNdiN0cVygz/muvkreWWem6BBw130+ODbRRgfiVveL0NizCEnKXkpvdeTy9Bxvo9LIKlhrw==", + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.3.2.tgz", + "integrity": "sha512-iaqOHS4f90KNADBHqVsRBjKpM6iSvsUg1q5GhWMK03loYLaDzftrEwcsl0OkSSnRhJvAsT7q4q3r3YzRoV0v1g==", + "license": "MIT", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "cross-inspect": "1.0.0", @@ -4020,6 +3973,7 @@ "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-10.0.5.tgz", "integrity": "sha512-Cbr5aYjr3HkwdPvetZp1cpDWTGdD1Owgsb3z/ClzhmrboiK86EnQDxDvOJiQkDCPWE9lNBwj8Y4HfxroY0D9DQ==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-tools/delegate": "^10.0.4", "@graphql-tools/schema": "^10.0.3", @@ -4038,6 +3992,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "license": "MIT", "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } @@ -4046,7 +4001,9 @@ "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { "@humanwhocodes/object-schema": "^2.0.2", "debug": "^4.3.1", @@ -4061,6 +4018,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4071,6 +4029,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4083,6 +4042,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -4092,16 +4052,19 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" }, "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" } @@ -4111,6 +4074,7 @@ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, + "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" }, @@ -4118,16 +4082,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@josephg/resolvable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz", - "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==" - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -4142,6 +4102,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -4151,42 +4112,41 @@ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "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/@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/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "dev": true - }, "node_modules/@kamilkisiela/fast-url-parser": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@kamilkisiela/fast-url-parser/-/fast-url-parser-1.1.4.tgz", "integrity": "sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", @@ -4206,6 +4166,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", "dependencies": { "debug": "4" }, @@ -4217,6 +4178,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", "dependencies": { "agent-base": "6", "debug": "4" @@ -4225,24 +4187,11 @@ "node": ">= 6" } }, - "node_modules/@mapbox/node-pre-gyp/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==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -4255,6 +4204,7 @@ "resolved": "https://registry.npmjs.org/@mole-inc/bin-wrapper/-/bin-wrapper-8.0.1.tgz", "integrity": "sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==", "dev": true, + "license": "MIT", "dependencies": { "bin-check": "^4.1.0", "bin-version-check": "^5.0.0", @@ -4270,33 +4220,36 @@ } }, "node_modules/@nerdware/ddb-single-table": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@nerdware/ddb-single-table/-/ddb-single-table-2.4.0.tgz", - "integrity": "sha512-WOZqVg2izxFvEyqSqgW1fh6gtR2aFm0DE+lyfhqwsaQ8s2cHb/t1NAqAOcwsTj7IL5a0e58AOVJzNFIMD5vqdQ==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@nerdware/ddb-single-table/-/ddb-single-table-2.6.3.tgz", + "integrity": "sha512-olm63B8NycLvHidvAH8lESuN6e+ZG0JUE/jTck899xkaG+j+UVl48Dc1OSy2TBoYPzjTyvIFCZGLv4TTqRDesw==", + "license": "MIT", "dependencies": { - "@aws-sdk/client-dynamodb": "^3.398.0", - "@aws-sdk/lib-dynamodb": "^3.398.0", - "@nerdware/ts-type-safety-utils": "^1.0.7", - "dayjs": "^1.11.9" + "@aws-sdk/client-dynamodb": "^3.572.0", + "@aws-sdk/lib-dynamodb": "^3.572.0", + "@nerdware/ts-type-safety-utils": "^1.0.13", + "dayjs": "^1.11.11" }, "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" + "node": ">=20.0.0", + "npm": ">=10.0.0" } }, "node_modules/@nerdware/ts-string-helpers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@nerdware/ts-string-helpers/-/ts-string-helpers-1.2.1.tgz", - "integrity": "sha512-0tBzMjXZbevJk7sWQd6gIUCuxCmfDQHuUXQWoXToRhKMYzx3mBPKFBmiOvkRr0mf5Ix5w3cIagPZIw7K2+vB1Q==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@nerdware/ts-string-helpers/-/ts-string-helpers-1.7.0.tgz", + "integrity": "sha512-Bd2nlmR+jeZEKJhGFABLhXfZwt/fw2+5zdCacb4zeOVmr3JYnLyAMqwbwz/4ophJVCtwrCKToU7YWDOcZziiQw==", + "license": "MIT", "engines": { "node": ">=16.0.0", "npm": ">=8.0.0" } }, "node_modules/@nerdware/ts-type-safety-utils": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@nerdware/ts-type-safety-utils/-/ts-type-safety-utils-1.0.9.tgz", - "integrity": "sha512-DU8oNGYGIfqDYPSXjDIViuyte4zke0gj8OtHIGOs23THQIDoLvThGUBBe8SCVaKl2OQk0hZzwKQfeJOYCbLGcQ==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@nerdware/ts-type-safety-utils/-/ts-type-safety-utils-1.0.14.tgz", + "integrity": "sha512-O0JgeN3D/2oTXTxYlSYrwguZQFVwNYRAr7gSNnpSQhApM1Sa+m/aWO/04JWnuE32UHh+288mFvLXZThR8zK/Ww==", + "license": "MIT", "engines": { "node": ">=18.0.0", "npm": ">=9.0.0" @@ -4307,6 +4260,7 @@ "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" @@ -4320,6 +4274,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -4329,6 +4284,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -4338,10 +4294,11 @@ } }, "node_modules/@oclif/core": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/@oclif/core/-/core-3.25.2.tgz", - "integrity": "sha512-OkW/cNa/3DhoCz2YlSpymVe8DXqkoRaLY4SPTVqNVzR4R1dFBE5KoCtuwKwnhxYLCRCqaViPgRnB5K26f0MnjA==", + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-3.27.0.tgz", + "integrity": "sha512-Fg93aNFvXzBq5L7ztVHFP2nYwWU1oTCq48G0TjF/qC1UN36KWa2H5Hsm72kERd5x/sjy2M2Tn4kDEorUlpXOlw==", "dev": true, + "license": "MIT", "dependencies": { "@types/cli-progress": "^3.11.5", "ansi-escapes": "^4.3.2", @@ -4351,15 +4308,15 @@ "clean-stack": "^3.0.1", "cli-progress": "^3.12.0", "color": "^4.2.3", - "debug": "^4.3.4", - "ejs": "^3.1.9", + "debug": "^4.3.5", + "ejs": "^3.1.10", "get-package-type": "^0.1.0", "globby": "^11.1.0", "hyperlinker": "^1.0.0", "indent-string": "^4.0.0", "is-wsl": "^2.2.0", "js-yaml": "^3.14.1", - "minimatch": "^9.0.3", + "minimatch": "^9.0.4", "natural-orderby": "^2.0.3", "object-treeify": "^1.1.33", "password-prompt": "^1.1.3", @@ -4381,6 +4338,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -4390,6 +4348,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4406,6 +4365,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -4418,6 +4378,7 @@ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "4.0.0" }, @@ -4433,6 +4394,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -4441,11 +4403,28 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@oclif/core/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@oclif/core/node_modules/slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -4463,6 +4442,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -4478,6 +4458,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -4491,1105 +4472,95 @@ } }, "node_modules/@oclif/plugin-help": { - "version": "6.0.18", - "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-6.0.18.tgz", - "integrity": "sha512-Ly0gu/+eq7GfIMT76cirbHgElYGlu+PaZ5elrAKmDiegBh31AXqaPQAj8PH4+sG8RSv5srYtrkrygZaw8IF9CQ==", + "version": "6.2.7", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-6.2.7.tgz", + "integrity": "sha512-gwrCZW0EjbMe6iIXrkXWpIcfoqo+uMvWRudV3nkwa7ARL2U2GWy8RQ3+bqXvqByauRUcbgv3D6+38lSWqmMwtA==", "dev": true, - "hasShrinkwrap": true, + "license": "MIT", "dependencies": { - "@oclif/core": "^3.23.0" + "@oclif/core": "^4" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@oclif/plugin-help/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==", + "node_modules/@oclif/plugin-help/node_modules/@oclif/core": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.0.17.tgz", + "integrity": "sha512-zfdSRip9DVMOklMojWCLZEB4iOzy7LDTABCDzCXqmpZGS+o1e1xts4jGhnte3mi0WV0YthNfYqF16tqk6CWITA==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "ansi-escapes": "^4.3.2", + "ansis": "^3.3.2", + "clean-stack": "^3.0.1", + "cli-spinners": "^2.9.2", + "debug": "^4.3.5", + "ejs": "^3.1.10", + "get-package-type": "^0.1.0", + "globby": "^11.1.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "lilconfig": "^3.1.2", + "minimatch": "^9.0.5", + "string-width": "^4.2.3", + "supports-color": "^8", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">= 8" + "node": ">=18.0.0" } }, - "node_modules/@oclif/plugin-help/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==", + "node_modules/@oclif/plugin-help/node_modules/clean-stack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", + "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", "dev": true, "license": "MIT", + "dependencies": { + "escape-string-regexp": "4.0.0" + }, "engines": { - "node": ">= 8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@oclif/plugin-help/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==", + "node_modules/@oclif/plugin-help/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": "MIT", + "license": "ISC", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 8" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@oclif/plugin-help/node_modules/@oclif/core": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@oclif/core/-/core-3.23.0.tgz", - "integrity": "sha512-giQ/8Ft8yXWg4IyPVtynPb7ihoQsa3A/1Q53UIJIhh+8k+EedE3lJ01yn6sq6Ha35IGqsG1WhkeHzlJIuldEaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/cli-progress": "^3.11.5", - "ansi-escapes": "^4.3.2", - "ansi-styles": "^4.3.0", - "cardinal": "^2.1.1", - "chalk": "^4.1.2", - "clean-stack": "^3.0.1", - "cli-progress": "^3.12.0", - "color": "^4.2.3", - "debug": "^4.3.4", - "ejs": "^3.1.9", - "get-package-type": "^0.1.0", - "globby": "^11.1.0", - "hyperlinker": "^1.0.0", - "indent-string": "^4.0.0", - "is-wsl": "^2.2.0", - "js-yaml": "^3.14.1", - "minimatch": "^9.0.3", - "natural-orderby": "^2.0.3", - "object-treeify": "^1.1.33", - "password-prompt": "^1.1.3", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "supports-color": "^8.1.1", - "supports-hyperlinks": "^2.2.0", - "widest-line": "^3.1.0", - "wordwrap": "^1.0.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@oclif/plugin-help/node_modules/@oclif/core/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@oclif/plugin-help/node_modules/@oclif/core/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@oclif/plugin-help/node_modules/@oclif/core/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@oclif/plugin-help/node_modules/@oclif/core/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/@oclif/plugin-help/node_modules/@types/cli-progress": { - "version": "3.11.5", - "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.5.tgz", - "integrity": "sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@oclif/plugin-help/node_modules/@types/cli-progress/node_modules/@types/node": { - "version": "20.5.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz", - "integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@oclif/plugin-help/node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@oclif/plugin-help/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": ">=8" - } - }, - "node_modules/@oclif/plugin-help/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": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@oclif/plugin-help/node_modules/ansicolors": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", - "dev": true, - "license": "MIT" - }, - "node_modules/@oclif/plugin-help/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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@oclif/plugin-help/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@oclif/plugin-help/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@oclif/plugin-help/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, - "license": "MIT", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/cardinal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", - "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", - "dev": true, - "license": "MIT", - "dependencies": { - "ansicolors": "~0.3.2", - "redeyed": "~2.1.0" - }, - "bin": { - "cdl": "bin/cdl.js" - } - }, - "node_modules/@oclif/plugin-help/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@oclif/plugin-help/node_modules/clean-stack": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", - "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@oclif/plugin-help/node_modules/cli-progress": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", - "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@oclif/plugin-help/node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/@oclif/plugin-help/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@oclif/plugin-help/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@oclif/plugin-help/node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/@oclif/plugin-help/node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@oclif/plugin-help/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@oclif/plugin-help/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, - "license": "MIT" - }, - "node_modules/@oclif/plugin-help/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": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@oclif/plugin-help/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/@oclif/plugin-help/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@oclif/plugin-help/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, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@oclif/plugin-help/node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/@oclif/plugin-help/node_modules/fastq": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", - "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/@oclif/plugin-help/node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/@oclif/plugin-help/node_modules/filelist/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@oclif/plugin-help/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, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@oclif/plugin-help/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@oclif/plugin-help/node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@oclif/plugin-help/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/@oclif/plugin-help/node_modules/hyperlinker": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hyperlinker/-/hyperlinker-1.0.0.tgz", - "integrity": "sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@oclif/plugin-help/node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@oclif/plugin-help/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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@oclif/plugin-help/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, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@oclif/plugin-help/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@oclif/plugin-help/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/@oclif/plugin-help/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true, - "license": "ISC" - }, - "node_modules/@oclif/plugin-help/node_modules/jake": { - "version": "10.8.5", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", - "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@oclif/plugin-help/node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/@oclif/plugin-help/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@oclif/plugin-help/node_modules/minimatch/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@oclif/plugin-help/node_modules/natural-orderby": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-2.0.3.tgz", - "integrity": "sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/@oclif/plugin-help/node_modules/object-treeify": { - "version": "1.1.33", - "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", - "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@oclif/plugin-help/node_modules/password-prompt": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/password-prompt/-/password-prompt-1.1.3.tgz", - "integrity": "sha512-HkrjG2aJlvF0t2BMH0e2LB/EHf3Lcq3fNMzy4GYHcQblAvOl+QQji1Lx7WRBMqpVK8p+KR7bCg7oqAMXtdgqyw==", - "dev": true, - "license": "0BSD", - "dependencies": { - "ansi-escapes": "^4.3.2", - "cross-spawn": "^7.0.3" - } - }, - "node_modules/@oclif/plugin-help/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": ">=8" - } - }, - "node_modules/@oclif/plugin-help/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@oclif/plugin-help/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/@oclif/plugin-help/node_modules/redeyed": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", - "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", - "dev": true, - "license": "MIT", - "dependencies": { - "esprima": "~4.0.0" - } - }, - "node_modules/@oclif/plugin-help/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, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/@oclif/plugin-help/node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/@oclif/plugin-help/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/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": ">=8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/@oclif/plugin-help/node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@oclif/plugin-help/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/@oclif/plugin-help/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": "BSD-3-Clause" - }, - "node_modules/@oclif/plugin-help/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": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/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": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/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/@oclif/plugin-help/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/@oclif/plugin-help/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@oclif/plugin-help/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": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@oclif/plugin-help/node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/@oclif/plugin-help/node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@oclif/plugin-help/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -5609,167 +4580,144 @@ } }, "node_modules/@oclif/plugin-plugins": { - "version": "3.9.4", - "resolved": "https://registry.npmjs.org/@oclif/plugin-plugins/-/plugin-plugins-3.9.4.tgz", - "integrity": "sha512-JtumjspRdzJgHk1S10wu68tdlqSnyYRmSgCsmsc6AEvU+Orb0DQfrAgJEO77rPKPNo5MfnVAj0WyCDTi0JT/vw==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/@oclif/plugin-plugins/-/plugin-plugins-5.4.2.tgz", + "integrity": "sha512-L85hRQJc8/c0l9/io4LqYOeWatq4L4g1tJswXGjlMW8ypxWpunYMnZVH6XxPKMjTPdlBVdkpelSgnJgggRlU7Q==", "dev": true, + "license": "MIT", "dependencies": { - "@oclif/core": "^2.15.0", - "chalk": "^4.1.2", - "debug": "^4.3.4", - "http-call": "^5.2.2", - "load-json-file": "^5.3.0", - "npm": "9.8.1", - "npm-run-path": "^4.0.1", - "semver": "^7.5.4", - "shelljs": "^0.8.5", - "tslib": "^2.6.2", - "validate-npm-package-name": "^5.0.0", - "yarn": "^1.22.18" + "@oclif/core": "^4", + "ansis": "^3.3.2", + "debug": "^4.3.6", + "npm": "^10.8.2", + "npm-package-arg": "^11.0.2", + "npm-run-path": "^5.3.0", + "object-treeify": "^4.0.1", + "semver": "^7.6.3", + "validate-npm-package-name": "^5.0.1", + "which": "^4.0.0", + "yarn": "^1.22.22" }, "engines": { - "node": ">=16" + "node": ">=18.0.0" } }, "node_modules/@oclif/plugin-plugins/node_modules/@oclif/core": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/@oclif/core/-/core-2.15.0.tgz", - "integrity": "sha512-fNEMG5DzJHhYmI3MgpByTvltBOMyFcnRIUMxbiz2ai8rhaYgaTHMG3Q38HcosfIvtw9nCjxpcQtC8MN8QtVCcA==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.0.17.tgz", + "integrity": "sha512-zfdSRip9DVMOklMojWCLZEB4iOzy7LDTABCDzCXqmpZGS+o1e1xts4jGhnte3mi0WV0YthNfYqF16tqk6CWITA==", "dev": true, + "license": "MIT", "dependencies": { - "@types/cli-progress": "^3.11.0", "ansi-escapes": "^4.3.2", - "ansi-styles": "^4.3.0", - "cardinal": "^2.1.1", - "chalk": "^4.1.2", + "ansis": "^3.3.2", "clean-stack": "^3.0.1", - "cli-progress": "^3.12.0", - "debug": "^4.3.4", - "ejs": "^3.1.8", + "cli-spinners": "^2.9.2", + "debug": "^4.3.5", + "ejs": "^3.1.10", "get-package-type": "^0.1.0", "globby": "^11.1.0", - "hyperlinker": "^1.0.0", "indent-string": "^4.0.0", "is-wsl": "^2.2.0", - "js-yaml": "^3.14.1", - "natural-orderby": "^2.0.3", - "object-treeify": "^1.1.33", - "password-prompt": "^1.1.2", - "slice-ansi": "^4.0.0", + "lilconfig": "^3.1.2", + "minimatch": "^9.0.5", "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "supports-color": "^8.1.1", - "supports-hyperlinks": "^2.2.0", - "ts-node": "^10.9.1", - "tslib": "^2.5.0", + "supports-color": "^8", "widest-line": "^3.1.0", "wordwrap": "^1.0.0", "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@oclif/plugin-plugins/node_modules/@oclif/core/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/@oclif/plugin-plugins/node_modules/clean-stack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", + "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "escape-string-regexp": "4.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@oclif/plugin-plugins/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/@oclif/plugin-plugins/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" + "license": "ISC", + "engines": { + "node": ">=16" } }, - "node_modules/@oclif/plugin-plugins/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@oclif/plugin-plugins/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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@oclif/plugin-plugins/node_modules/clean-stack": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", - "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", + "node_modules/@oclif/plugin-plugins/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, + "license": "MIT", "dependencies": { - "escape-string-regexp": "4.0.0" + "path-key": "^4.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@oclif/plugin-plugins/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@oclif/plugin-plugins/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/@oclif/plugin-plugins/node_modules/object-treeify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-4.0.1.tgz", + "integrity": "sha512-Y6tg5rHfsefSkfKujv2SwHulInROy/rCL5F4w0QOWxut8AnxYxf0YmNhTh95Zfyxpsudo66uqkux0ACFnyMSgQ==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 16" } }, - "node_modules/@oclif/plugin-plugins/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/@oclif/plugin-plugins/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@oclif/plugin-plugins/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -5777,21 +4725,36 @@ "node": ">=10" } }, - "node_modules/@oclif/plugin-plugins/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "node_modules/@oclif/plugin-plugins/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@oclif/plugin-plugins/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" } }, "node_modules/@oclif/plugin-plugins/node_modules/wrap-ansi": { @@ -5799,6 +4762,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5812,10 +4776,11 @@ } }, "node_modules/@peculiar/asn1-schema": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.8.tgz", - "integrity": "sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==", + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.13.tgz", + "integrity": "sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==", "dev": true, + "license": "MIT", "dependencies": { "asn1js": "^3.0.5", "pvtsutils": "^1.3.5", @@ -5827,6 +4792,7 @@ "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, @@ -5835,16 +4801,17 @@ } }, "node_modules/@peculiar/webcrypto": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.5.tgz", - "integrity": "sha512-oDk93QCDGdxFRM8382Zdminzs44dg3M2+E5Np+JWkpqLDyJC9DviMh8F8mEJkYuUcUOGA5jHO5AJJ10MFWdbZw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", + "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", "dev": true, + "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.3.8", "@peculiar/json-schema": "^1.1.12", "pvtsutils": "^1.3.5", "tslib": "^2.6.2", - "webcrypto-core": "^1.7.8" + "webcrypto-core": "^1.8.0" }, "engines": { "node": ">=10.12.0" @@ -5853,27 +4820,32 @@ "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -5882,33 +4854,39 @@ "node_modules/@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" }, "node_modules/@redocly/ajv": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.0.tgz", "integrity": "sha512-9GWx27t7xWhDIR02PA18nzBdLcKQRgc46xNQvjFkrYk4UOmvKhJ/dawwiX0cCOeetN5LcaaiqQbVOWYK62SGHw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -5920,27 +4898,132 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@redocly/ajv/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==", - "dev": true + "node_modules/@redocly/cli": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-1.19.0.tgz", + "integrity": "sha512-ev6J0eD+quprvW9PVCl9JmRFZbj6cuK+mnYPAjcrPvesy2RF752fflcpgQjGnyFaGb1Cj+DiwDi3dYr3EAp04A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/openapi-core": "1.19.0", + "abort-controller": "^3.0.0", + "chokidar": "^3.5.1", + "colorette": "^1.2.0", + "core-js": "^3.32.1", + "form-data": "^4.0.0", + "get-port-please": "^3.0.1", + "glob": "^7.1.6", + "handlebars": "^4.7.6", + "mobx": "^6.0.4", + "node-fetch": "^2.6.1", + "pluralize": "^8.0.0", + "react": "^17.0.0 || ^18.2.0", + "react-dom": "^17.0.0 || ^18.2.0", + "redoc": "~2.1.5", + "semver": "^7.5.2", + "simple-websocket": "^9.0.0", + "styled-components": "^6.0.7", + "yargs": "17.0.1" + }, + "bin": { + "openapi": "bin/cli.js", + "redocly": "bin/cli.js" + }, + "engines": { + "node": ">=14.19.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@redocly/cli/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/@redocly/cli/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@redocly/cli/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@redocly/cli/node_modules/yargs": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz", + "integrity": "sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@redocly/cli/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } }, "node_modules/@redocly/config": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.1.3.tgz", - "integrity": "sha512-DjgGwhyolxDLO7hP1V8h6qQUUTkqN7P/xG2OMgsABJ1Pr40GG32+cqx9oBbKX9iN+JXh6kVUZgBMcPPB1fbyLA==", - "dev": true + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.7.0.tgz", + "integrity": "sha512-6GKxTo/9df0654Mtivvr4lQnMOp+pRj9neVywmI5+BwfZLTtkJnj2qB3D6d8FHTr4apsNOf6zTa5FojX0Evh4g==", + "dev": true, + "license": "MIT" }, "node_modules/@redocly/openapi-core": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.10.4.tgz", - "integrity": "sha512-GzvAuoVtHk75q/HqaRNqRUTZWYKpZ16HCOWIrx2txAvZrMoWCUNRVsELY91W/ilH/Cepj6t/Nh+bpJ7o/mcN/g==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.19.0.tgz", + "integrity": "sha512-ezK6qr80sXvjDgHNrk/zmRs9vwpIAeHa0T/qmo96S+ib4ThQ5a8f3qjwEqxMeVxkxCTbkaY9sYSJKOxv4ejg5w==", "dev": true, + "license": "MIT", "dependencies": { "@redocly/ajv": "^8.11.0", - "@redocly/config": "^0.1.1", + "@redocly/config": "^0.7.0", "colorette": "^1.2.0", + "https-proxy-agent": "^7.0.4", "js-levenshtein": "^1.1.6", "js-yaml": "^4.1.0", "lodash.isequal": "^4.5.0", @@ -5954,252 +5037,311 @@ "npm": ">=7.0.0" } }, - "node_modules/@redocly/openapi-core/node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true - }, - "node_modules/@redocly/openapi-core/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@repeaterjs/repeater": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.5.tgz", - "integrity": "sha512-l3YHBLAol6d/IKnB9LhpD0cEZWAoe3eFKUyTYWmFmCO2Q/WOckxLQAUyMZWwZV2M/m3+4vgRoaolFqaII82/TA==", - "dev": true + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.6.tgz", + "integrity": "sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==", + "dev": true, + "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", - "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", + "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", - "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", + "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", - "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", + "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", - "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", + "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", - "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", + "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", + "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", - "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", + "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", - "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", + "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", + "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", - "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", + "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", + "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", - "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", + "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", - "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", + "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", - "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", + "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", - "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", + "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", - "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", + "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@sentry-internal/tracing": { - "version": "7.107.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.107.0.tgz", - "integrity": "sha512-le9wM8+OHBbq7m/8P7JUJ1UhSPIty+Z/HmRXc5Z64ODZcOwFV6TmDpYx729IXDdz36XUKmeI+BeM7yQdTTZPfQ==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.118.0.tgz", + "integrity": "sha512-dERAshKlQLrBscHSarhHyUeGsu652bDTUN1FK0m4e3X48M3I5/s+0N880Qjpe5MprNLcINlaIgdQ9jkisvxjfw==", + "license": "MIT", "dependencies": { - "@sentry/core": "7.107.0", - "@sentry/types": "7.107.0", - "@sentry/utils": "7.107.0" + "@sentry/core": "7.118.0", + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/core": { - "version": "7.107.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.107.0.tgz", - "integrity": "sha512-C7ogye6+KPyBi8NVL0P8Rxx3Ur7Td8ufnjxosVy678lqY+dcYPk/HONROrzUFYW5fMKWL4/KYnwP+x9uHnkDmw==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.118.0.tgz", + "integrity": "sha512-ol0xBdp3/K11IMAYSQE0FMxBOOH9hMsb/rjxXWe0hfM5c72CqYWL3ol7voPci0GELJ5CZG+9ImEU1V9r6gK64g==", + "license": "MIT", + "dependencies": { + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/integrations": { + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.118.0.tgz", + "integrity": "sha512-C2rR4NvIMjokF8jP5qzSf1o2zxDx7IeYnr8u15Kb2+HdZtX559owALR0hfgwnfeElqMhGlJBaKUWZ48lXJMzCQ==", + "license": "MIT", "dependencies": { - "@sentry/types": "7.107.0", - "@sentry/utils": "7.107.0" + "@sentry/core": "7.118.0", + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0", + "localforage": "^1.8.1" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/node": { - "version": "7.107.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.107.0.tgz", - "integrity": "sha512-UZXkG7uThT2YyPW8AOSKRXp1LbVcBHufa4r1XAwBukA2FKO6HHJPjMUgY6DYVQ6k+BmA56CNfVjYrdLbyjBYYA==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.118.0.tgz", + "integrity": "sha512-79N63DvYKkNPqzmc0cjO+vMZ/nU7+CbE3K3COQNiV7gk58+666G9mRZQJuZVOVebatq5wM5UR0G4LPkwD+J84g==", + "license": "MIT", "dependencies": { - "@sentry-internal/tracing": "7.107.0", - "@sentry/core": "7.107.0", - "@sentry/types": "7.107.0", - "@sentry/utils": "7.107.0" + "@sentry-internal/tracing": "7.118.0", + "@sentry/core": "7.118.0", + "@sentry/integrations": "7.118.0", + "@sentry/types": "7.118.0", + "@sentry/utils": "7.118.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/types": { - "version": "7.107.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.107.0.tgz", - "integrity": "sha512-H7qcPjPSUWHE/Zf5bR1EE24G0pGVuJgrSx8Tvvl5nKEepswMYlbXHRVSDN0gTk/E5Z7cqf+hUBOpkQgZyps77w==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.118.0.tgz", + "integrity": "sha512-2drqrD2+6kgeg+W/ycmiti3G4lJrV3hGjY9PpJ3bJeXrh6T2+LxKPzlgSEnKFaeQWkXdZ4eaUbtTXVebMjb5JA==", + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/@sentry/utils": { - "version": "7.107.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.107.0.tgz", - "integrity": "sha512-C6PbN5gHh73MRHohnReeQ60N8rrLYa9LciHue3Ru2290eSThg4CzsPnx4SzkGpkSeVlhhptKtKZ+hp/ha3iVuw==", + "version": "7.118.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.118.0.tgz", + "integrity": "sha512-43qItc/ydxZV1Zb3Kn2M54RwL9XXFa3IAYBO8S82Qvq5YUYmU2AmJ1jgg7DabXlVSWgMA1HntwqnOV3JLaEnTQ==", + "license": "MIT", "dependencies": { - "@sentry/types": "7.107.0" + "@sentry/types": "7.118.0" }, "engines": { "node": ">=8" @@ -6210,6 +5352,7 @@ "resolved": "https://registry.npmjs.org/@serverless-guru/prettier-plugin-import-order/-/prettier-plugin-import-order-0.4.2.tgz", "integrity": "sha512-vRsSDd5Std8KTmDFWiZqGNuS2UmsZ4QxnepzGuOLwWlTj1OC976NnDjF7QtVCYAwSMj+IWJ93PWB74LHQ0x5MQ==", "dev": true, + "license": "APACHE LICENSE, VERSION 2.0", "dependencies": { "@babel/core": "^7.21.4", "@babel/generator": "^7.21.4", @@ -6234,13 +5377,15 @@ "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -6253,6 +5398,7 @@ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" } @@ -6262,6 +5408,7 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -6271,6 +5418,7 @@ "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^2.0.0", "lodash.get": "^4.4.2", @@ -6282,6 +5430,7 @@ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" } @@ -6290,476 +5439,518 @@ "version": "0.7.2", "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true + "dev": true, + "license": "(Unlicense OR Apache-2.0)" }, "node_modules/@smithy/abort-controller": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.2.0.tgz", - "integrity": "sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/config-resolver": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.2.0.tgz", - "integrity": "sha512-fsiMgd8toyUba6n1WRmr+qACzXltpdDkPTAaDqc8QqPBUzO+/JKwL6bUBseHVi8tu9l+3JOK+tSf7cay+4B3LA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", + "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^2.3.0", - "@smithy/types": "^2.12.0", - "@smithy/util-config-provider": "^2.3.0", - "@smithy/util-middleware": "^2.2.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/core": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.4.0.tgz", - "integrity": "sha512-uu9ZDI95Uij4qk+L6kyFjdk11zqBkcJ3Lv0sc6jZrqHvLyr0+oeekD3CnqMafBn/5PRI6uv6ulW3kNLRBUHeVw==", - "dependencies": { - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.2.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/util-middleware": "^2.2.0", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.3.2.tgz", + "integrity": "sha512-in5wwt6chDBcUv1Lw1+QzZxN9fBffi+qOixfb65yK4sDuKG7zAUO9HAFqmVzsZM3N+3tTyvZjtnDXePpvp007Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/credential-provider-imds": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.3.0.tgz", - "integrity": "sha512-BWB9mIukO1wjEOo1Ojgl6LrG4avcaC7T/ZP6ptmAaW4xluhSIPZhY+/PI5YKzlk+jsm+4sQZB45Bt1OfMeQa3w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", + "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/eventstream-codec": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.2.0.tgz", - "integrity": "sha512-8janZoJw85nJmQZc4L8TuePp2pk1nxLgkxIR0TUjKJ5Dkj5oelB9WtiSSGXCQvNsJl0VSTvK/2ueMXxvpa9GVw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", + "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@smithy/types": "^2.12.0", - "@smithy/util-hex-encoding": "^2.2.0", + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-2.2.0.tgz", - "integrity": "sha512-UaPf8jKbcP71BGiO0CdeLmlg+RhWnlN8ipsMSdwvqBFigl5nil3rHOI/5GE3tfiuX8LvY5Z9N0meuU7Rab7jWw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.5.tgz", + "integrity": "sha512-dEyiUYL/ekDfk+2Ra4GxV+xNnFoCmk1nuIXg+fMChFTrM2uI/1r9AdiTYzPqgb72yIv/NtAj6C3dG//1wwgakQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^2.2.0", - "@smithy/types": "^2.12.0", + "@smithy/eventstream-serde-universal": "^3.0.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-2.2.0.tgz", - "integrity": "sha512-RHhbTw/JW3+r8QQH7PrganjNCiuiEZmpi6fYUAetFfPLfZ6EkiA08uN3EFfcyKubXQxOwTeJRZSQmDDCdUshaA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz", + "integrity": "sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-2.2.0.tgz", - "integrity": "sha512-zpQMtJVqCUMn+pCSFcl9K/RPNtQE0NuMh8sKpCdEHafhwRsjP50Oq/4kMmvxSRy6d8Jslqd8BLvDngrUtmN9iA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.4.tgz", + "integrity": "sha512-mjlG0OzGAYuUpdUpflfb9zyLrBGgmQmrobNT8b42ZTsGv/J03+t24uhhtVEKG/b2jFtPIHF74Bq+VUtbzEKOKg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^2.2.0", - "@smithy/types": "^2.12.0", + "@smithy/eventstream-serde-universal": "^3.0.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-2.2.0.tgz", - "integrity": "sha512-pvoe/vvJY0mOpuF84BEtyZoYfbehiFj8KKWk1ds2AT0mTLYFVs+7sBJZmioOFdBXKd48lfrx1vumdPdmGlCLxA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.4.tgz", + "integrity": "sha512-Od9dv8zh3PgOD7Vj4T3HSuox16n0VG8jJIM2gvKASL6aCtcS8CfHZDWe1Ik3ZXW6xBouU+45Q5wgoliWDZiJ0A==", + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^2.2.0", - "@smithy/types": "^2.12.0", + "@smithy/eventstream-codec": "^3.1.2", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/fetch-http-handler": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.5.0.tgz", - "integrity": "sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^3.3.0", - "@smithy/querystring-builder": "^2.2.0", - "@smithy/types": "^2.12.0", - "@smithy/util-base64": "^2.3.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/hash-node": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.2.0.tgz", - "integrity": "sha512-zLWaC/5aWpMrHKpoDF6nqpNtBhlAYKF/7+9yMN7GpdR8CzohnWfGtMznPybnwSS8saaXBMxIGwJqR4HmRp6b3g==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", + "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", - "@smithy/util-buffer-from": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", + "@smithy/types": "^3.3.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/invalid-dependency": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.2.0.tgz", - "integrity": "sha512-nEDASdbKFKPXN2O6lOlTgrEEOO9NHIeO+HVvZnkqc8h5U9g3BIhWsvzFo+UcUbliMHvKNPD/zVxDrkP1Sbgp8Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", + "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-content-length": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.2.0.tgz", - "integrity": "sha512-5bl2LG1Ah/7E5cMSC+q+h3IpVHMeOkG0yLRyQT1p2aMJkSrZG7RlXHPuAgb7EyaFeidKEnnd/fNaLLaKlHGzDQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", + "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^3.3.0", - "@smithy/types": "^2.12.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-endpoint": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.5.0.tgz", - "integrity": "sha512-OBhI9ZEAG8Xen0xsFJwwNOt44WE2CWkfYIxTognC8x42Lfsdf0VN/wCMqpdkySMDio/vts10BiovAxQp0T0faA==", - "dependencies": { - "@smithy/middleware-serde": "^2.3.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-middleware": "^2.2.0", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-retry": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.2.0.tgz", - "integrity": "sha512-PsjDOLpbevgn37yJbawmfVoanru40qVA8UEf2+YA1lvOefmhuhL6ZbKtGsLAWDRnE1OlAmedsbA/htH6iSZjNA==", - "dependencies": { - "@smithy/node-config-provider": "^2.3.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/service-error-classification": "^2.1.5", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.14.tgz", + "integrity": "sha512-7ZaWZJOjUxa5hgmuMspyt8v/zVsh0GXYuF7OvCmdcbVa/xbnKQoYC+uYKunAqRGTkxjOyuOCw9rmFUFOqqC0eQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", "tslib": "^2.6.2", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-retry/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } }, "node_modules/@smithy/middleware-serde": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.3.0.tgz", - "integrity": "sha512-sIADe7ojwqTyvEQBe1nc/GXB9wdHhi9UwyX0lTyttmUWDJLP655ZYE1WngnNyXREme8I27KCaUhyhZWRXL0q7Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.2.0.tgz", - "integrity": "sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/node-config-provider": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.3.0.tgz", - "integrity": "sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", + "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/node-http-handler": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.5.0.tgz", - "integrity": "sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/querystring-builder": "^2.2.0", - "@smithy/types": "^2.12.0", + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/property-provider": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.2.0.tgz", - "integrity": "sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", + "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/protocol-http": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.3.0.tgz", - "integrity": "sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/querystring-builder": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.2.0.tgz", - "integrity": "sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", - "@smithy/util-uri-escape": "^2.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/querystring-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.2.0.tgz", - "integrity": "sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/service-error-classification": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.1.5.tgz", - "integrity": "sha512-uBDTIBBEdAQryvHdc5W8sS5YX7RQzF683XrHePVdFmAgKiMofU15FLSM0/HU03hKTnazdNRFa0YHS7+ArwoUSQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", + "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0" + "@smithy/types": "^3.3.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.4.0.tgz", - "integrity": "sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", + "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/signature-v4": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.2.0.tgz", - "integrity": "sha512-+B5TNzj/fRZzVW3z8UUJOkNx15+4E0CLuvJmJUA1JUIZFp3rdJ/M2H5r2SqltaVPXL0oIxv/6YK92T9TsFGbFg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^2.2.0", - "@smithy/is-array-buffer": "^2.2.0", - "@smithy/types": "^2.12.0", - "@smithy/util-hex-encoding": "^2.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-uri-escape": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/smithy-client": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.5.0.tgz", - "integrity": "sha512-DDXWHWdimtS3y/Kw1Jo46KQ0ZYsDKcldFynQERUGBPDpkW1lXOTHy491ALHjwfiBQvzsVKVxl5+ocXNIgJuX4g==", - "dependencies": { - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/types": "^2.12.0", - "@smithy/util-stream": "^2.2.0", + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.12.tgz", + "integrity": "sha512-wtm8JtsycthkHy1YA4zjIh2thJgIQ9vGkoR639DBx5lLlLNU0v4GARpQZkr2WjXue74nZ7MiTSWfVrLkyD8RkA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/types": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", - "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/url-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.2.0.tgz", - "integrity": "sha512-hoA4zm61q1mNTpksiSWp2nEl1dt3j726HdRhiNgVJQMj7mLp7dprtF57mOB6JvEk/x9d2bsuL5hlqZbBuHQylQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^2.2.0", - "@smithy/types": "^2.12.0", + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/util-base64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.3.0.tgz", - "integrity": "sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-body-length-browser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.2.0.tgz", - "integrity": "sha512-dtpw9uQP7W+n3vOtx0CfBD5EWd7EPdIdsQnWTDoFf77e3VUf05uA7R7TGipIo8e4WL2kuPdnsr3hMQn9ziYj5w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" } }, "node_modules/@smithy/util-body-length-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.3.0.tgz", - "integrity": "sha512-ITWT1Wqjubf2CJthb0BuT9+bpzBfXeMokH/AAa5EJQgbv9aPMVfnM76iFIZVFf50hYXGbtiV71BHAthNWd6+dw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-config-provider": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.3.0.tgz", - "integrity": "sha512-HZkzrRcuFN1k70RLqlNK4FnPXKOpkik1+4JaBoHNJn+RnJGYqaa3c5/+XtLOXhlKzlRgNvyaLieHTW2VwGN0VQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.2.0.tgz", - "integrity": "sha512-2okTdZaCBvOJszAPU/KSvlimMe35zLOKbQpHhamFJmR7t95HSe0K3C92jQPjKY3PmDBD+7iMkOnuW05F5OlF4g==", + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.14.tgz", + "integrity": "sha512-0iwTgKKmAIf+vFLV8fji21Jb2px11ktKVxbX6LIDPAUJyWQqGqBVfwba7xwa1f2FZUoolYQgLvxQEpJycXuQ5w==", + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^2.2.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -6768,16 +5959,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.3.0.tgz", - "integrity": "sha512-hfKXnNLmsW9cmLb/JXKIvtuO6Cf4SuqN5PN1C2Ru/TBIws+m1wSgb+A53vo0r66xzB6E82inKG2J7qtwdi+Kkw==", - "dependencies": { - "@smithy/config-resolver": "^2.2.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.14.tgz", + "integrity": "sha512-e9uQarJKfXApkTMMruIdxHprhcXivH1flYCe8JRDTzkkLx8dA3V5J8GZlST9yfDiRWkJpZJlUXGN9Rc9Ade3OQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^3.0.5", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -6785,113 +5977,122 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.2.0.tgz", - "integrity": "sha512-BuDHv8zRjsE5zXd3PxFXFknzBG3owCpjq8G3FcsXW3CykYXuEqM3nTSsmLzw5q+T12ZYuDlVUZKBdpNbhVtlrQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", + "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^2.3.0", - "@smithy/types": "^2.12.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">= 14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-hex-encoding": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.2.0.tgz", - "integrity": "sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-middleware": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.2.0.tgz", - "integrity": "sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-retry": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.2.0.tgz", - "integrity": "sha512-q9+pAFPTfftHXRytmZ7GzLFFrEGavqapFc06XxzZFcSIGERXMerXxCitjOG1prVDR9QdjqotF40SWvbqcCpf8g==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", + "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", + "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^2.1.5", - "@smithy/types": "^2.12.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">= 14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.2.0.tgz", - "integrity": "sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-buffer-from": "^2.2.0", - "@smithy/util-hex-encoding": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-uri-escape": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.2.0.tgz", - "integrity": "sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-waiter": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-2.2.0.tgz", - "integrity": "sha512-IHk53BVw6MPMi2Gsn+hCng8rFA3ZmR3Rk7GllxDUW9qFJl/hiSvskn7XldkECapQVkIg/1dHpMAxI9xSTaLLSA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.2.tgz", + "integrity": "sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^2.2.0", - "@smithy/types": "^2.12.0", + "@smithy/abort-controller": "^3.1.1", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@swc/cli": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.3.10.tgz", - "integrity": "sha512-YWfYo9kXdbmIuGwIPth9geKgb0KssCMTdZa44zAN5KoqcuCP2rTW9s60heQDSRNpbtCmUr7BKF1VivsoHXrvrQ==", + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.3.14.tgz", + "integrity": "sha512-0vGqD6FSW67PaZUZABkA+ADKsX7OUY/PwNEz1SbQdCvVk/e4Z36Gwh7mFVBQH9RIsMonTyhV1RHkwkGnEfR3zQ==", "dev": true, + "license": "MIT", "dependencies": { "@mole-inc/bin-wrapper": "^8.0.1", "@swc/counter": "^0.1.3", @@ -6921,26 +6122,28 @@ } } }, - "node_modules/@swc/cli/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/@swc/cli/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": { - "yallist": "^4.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@swc/cli/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -6949,14 +6152,15 @@ } }, "node_modules/@swc/core": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.8.tgz", - "integrity": "sha512-uY2RSJcFPgNOEg12RQZL197LZX+MunGiKxsbxmh22VfVxrOYGRvh4mPANFlrD1yb38CgmW1wI6YgIi8LkIwmWg==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.5.tgz", + "integrity": "sha512-qKK0/Ta4qvxs/ok3XyYVPT7OBenwRn1sSINf1cKQTBHPqr7U/uB4k2GTl6JgEs8H4PiJrMTNWfMLTucIoVSfAg==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@swc/counter": "^0.1.2", - "@swc/types": "^0.1.5" + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.12" }, "engines": { "node": ">=10" @@ -6966,19 +6170,19 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.4.8", - "@swc/core-darwin-x64": "1.4.8", - "@swc/core-linux-arm-gnueabihf": "1.4.8", - "@swc/core-linux-arm64-gnu": "1.4.8", - "@swc/core-linux-arm64-musl": "1.4.8", - "@swc/core-linux-x64-gnu": "1.4.8", - "@swc/core-linux-x64-musl": "1.4.8", - "@swc/core-win32-arm64-msvc": "1.4.8", - "@swc/core-win32-ia32-msvc": "1.4.8", - "@swc/core-win32-x64-msvc": "1.4.8" + "@swc/core-darwin-arm64": "1.7.5", + "@swc/core-darwin-x64": "1.7.5", + "@swc/core-linux-arm-gnueabihf": "1.7.5", + "@swc/core-linux-arm64-gnu": "1.7.5", + "@swc/core-linux-arm64-musl": "1.7.5", + "@swc/core-linux-x64-gnu": "1.7.5", + "@swc/core-linux-x64-musl": "1.7.5", + "@swc/core-win32-arm64-msvc": "1.7.5", + "@swc/core-win32-ia32-msvc": "1.7.5", + "@swc/core-win32-x64-msvc": "1.7.5" }, "peerDependencies": { - "@swc/helpers": "^0.5.0" + "@swc/helpers": "*" }, "peerDependenciesMeta": { "@swc/helpers": { @@ -6987,13 +6191,14 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.8.tgz", - "integrity": "sha512-hhQCffRTgzpTIbngSnC30vV6IJVTI9FFBF954WEsshsecVoCGFiMwazBbrkLG+RwXENTrMhgeREEFh6R3KRgKQ==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.5.tgz", + "integrity": "sha512-Y+bvW9C4/u26DskMbtQKT4FU6QQenaDYkKDi028vDIKAa7v1NZqYG9wmhD/Ih7n5EUy2uJ5I5EWD7WaoLzT6PA==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -7003,13 +6208,14 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.8.tgz", - "integrity": "sha512-P3ZBw8Jr8rKhY/J8d+6WqWriqngGTgHwtFeJ8MIakQJTbdYbFgXSZxcvDiERg3psbGeFXaUaPI0GO6BXv9k/OQ==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.5.tgz", + "integrity": "sha512-AuIbDlcaAhYS6mtF4UqvXgrLeAfXZbVf4pgtgShPbutF80VbCQiIB55zOFz5aZdCpsBVuCWcBq0zLneK+VQKkQ==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -7019,13 +6225,14 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.8.tgz", - "integrity": "sha512-PP9JIJt19bUWhAGcQW6qMwTjZOcMyzkvZa0/LWSlDm0ORYVLmDXUoeQbGD3e0Zju9UiZxyulnpjEN0ZihJgPTA==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.5.tgz", + "integrity": "sha512-99uBPHITRqgGwCXAjHY94VaV3Z40+D2NQNgR1t6xQpO8ZnevI6YSzX6GVZfBnV7+7oisiGkrVEwfIRRa+1s8FA==", "cpu": [ "arm" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -7035,13 +6242,14 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.8.tgz", - "integrity": "sha512-HvEWnwKHkoVUr5iftWirTApFJ13hGzhAY2CMw4lz9lur2m+zhPviRRED0FCI6T95Knpv7+8eUOr98Z7ctrG6DQ==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.5.tgz", + "integrity": "sha512-xHL3Erlz+OGGCG4h6K2HWiR56H5UYMuBWWPbbUufi2bJpfhuKQy/X3vWffwL8ZVfJmCUwr4/G91GHcm32uYzRg==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -7051,13 +6259,14 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.8.tgz", - "integrity": "sha512-kY8+qa7k/dEeBq9p0Hrta18QnJPpsiJvDQSLNaTIFpdM3aEM9zbkshWz8gaX5VVGUEALowCBUWqmzO4VaqM+2w==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.5.tgz", + "integrity": "sha512-5ArGdqvFMszNHdi4a67vopeYq8d1K+FuTWDrblHrAvZFhAyv+GQz2PnKqYOgl0sWmQxsNPfNwBFtxACpUO3Jzg==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -7067,13 +6276,14 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.8.tgz", - "integrity": "sha512-0WWyIw432wpO/zeGblwq4f2YWam4pn8Z/Ig4KzHMgthR/KmiLU3f0Z7eo45eVmq5vcU7Os1zi/Zb65OOt09q/w==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.5.tgz", + "integrity": "sha512-mSVVV/PFzCGtI1nVQQyx34NwCMgSurF6ZX/me8pUAX054vsE/pSFL66xN+kQOe/1Z/LOd4UmXFkZ/EzOSnYcSg==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -7083,13 +6293,14 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.8.tgz", - "integrity": "sha512-p4yxvVS05rBNCrBaSTa20KK88vOwtg8ifTW7ec/yoab0bD5EwzzB8KbDmLLxE6uziFa0sdjF0dfRDwSZPex37Q==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.5.tgz", + "integrity": "sha512-09hY3ZKMUORXVunESKS9yuP78+gQbr759GKHo8wyCdtAx8lCZdEjfI5NtC7/1VqwfeE32/U6u+5MBTVhZTt0AA==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -7099,13 +6310,14 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.8.tgz", - "integrity": "sha512-jKuXihxAaqUnbFfvPxtmxjdJfs87F1GdBf33il+VUmSyWCP4BE6vW+/ReDAe8sRNsKyrZ3UH1vI5q1n64csBUA==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.5.tgz", + "integrity": "sha512-B/UDtPI3RlYRFW42xQxOpl6kI/9LtkD7No+XeRIKQTPe15EP2o+rUlv7CmKljVBXgJ8KmaQbZlaEh1YP+QZEEQ==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -7115,13 +6327,14 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.8.tgz", - "integrity": "sha512-O0wT4AGHrX8aBeH6c2ADMHgagAJc5Kf6W48U5moyYDAkkVnKvtSc4kGhjWhe1Yl0sI0cpYh2In2FxvYsb44eWw==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.5.tgz", + "integrity": "sha512-BgLesVGmIY6Nub/sURqtSRvWYcbCE/ACfuZB3bZHVKD6nsZJJuOpdB8oC41fZPyc8yZUzL3XTBIifkT2RP+w9w==", "cpu": [ "ia32" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -7131,13 +6344,14 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.8.tgz", - "integrity": "sha512-C2AYc3A2o+ECciqsJWRgIpp83Vk5EaRzHe7ed/xOWzVd0MsWR+fweEsyOjlmzHfpUxJSi46Ak3/BIZJlhZbXbg==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.5.tgz", + "integrity": "sha512-CnF557tidLfQRPczcqDJ8x+LBQYsFa0Ra6w2+YU1iFUboaI2jJVuqt3vEChu80y6JiRIBAaaV2L/GawDJh1dIQ==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -7150,19 +6364,25 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/@swc/types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", - "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", - "dev": true + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.12.tgz", + "integrity": "sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", "dev": true, + "license": "MIT", "dependencies": { "defer-to-connect": "^2.0.0" }, @@ -7174,37 +6394,43 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "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, + "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/bcrypt": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -7213,6 +6439,7 @@ "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -7223,6 +6450,7 @@ "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", "dev": true, + "license": "MIT", "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", @@ -7231,10 +6459,11 @@ } }, "node_modules/@types/cli-progress": { - "version": "3.11.5", - "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.5.tgz", - "integrity": "sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g==", + "version": "3.11.6", + "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.6.tgz", + "integrity": "sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -7243,6 +6472,7 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -7251,13 +6481,15 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/cors": { "version": "2.8.17", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -7266,12 +6498,14 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -7280,9 +6514,10 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.43", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", - "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", + "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -7294,48 +6529,42 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" }, "node_modules/@types/js-yaml": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/json-stable-stringify": { - "version": "1.0.36", - "resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.36.tgz", - "integrity": "sha512-b7bq23s4fgBB76n34m2b3RBf6M369B0Z9uRR8aHTMd8kZISRkmDEpPD8hhpYvDFzr3bJCPES96cm3Q6qRNDbQw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/jsonwebtoken": { "version": "9.0.6", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -7345,21 +6574,24 @@ "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/lodash": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", - "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==", - "dev": true + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", + "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/lodash.merge": { "version": "4.6.9", "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.9.tgz", "integrity": "sha512-23sHDPmzd59kUgWyKGiOMO2Qb9YtqRO/x4IhkgNUiPQ1+5MUVqi6bCZeq9nBJ17msjIMbEIO5u+XW4Kz6aGUhQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/lodash": "*" } @@ -7367,23 +6599,27 @@ "node_modules/@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" }, "node_modules/@types/node": { - "version": "20.11.28", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz", - "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==", + "version": "20.14.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.14.tgz", + "integrity": "sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==", + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } @@ -7392,53 +6628,53 @@ "version": "2.6.11", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "license": "MIT", "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "node_modules/@types/qs": { - "version": "6.9.12", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.12.tgz", - "integrity": "sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg==" + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" }, "node_modules/@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true - }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", - "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", "dependencies": { "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/sinon": { @@ -7446,6 +6682,7 @@ "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.20.tgz", "integrity": "sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg==", "dev": true, + "license": "MIT", "dependencies": { "@types/sinonjs__fake-timers": "*" } @@ -7454,17 +6691,27 @@ "version": "8.1.5", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/superagent": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.4.tgz", - "integrity": "sha512-uzSBYwrpal8y2X2Pul5ZSWpzRiDha2FLcquaN95qUPnOjYgm/zQ5LIdqeJpQJTRWNTN+Rhm0aC8H06Ds2rqCYw==", + "version": "8.1.8", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.8.tgz", + "integrity": "sha512-nTqHJ2OTa7PFEpLahzSEEeFeqbMpmcN7OeayiOc7v+xk+/vyTKljRe+o4MPqSnPeRCMvtxuLG+5QqluUVQJOnA==", "dev": true, + "license": "MIT", "dependencies": { "@types/cookiejar": "^2.1.5", "@types/methods": "^1.1.4", - "@types/node": "*" + "@types/node": "*", + "form-data": "^4.0.0" } }, "node_modules/@types/supertest": { @@ -7472,6 +6719,7 @@ "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", "dev": true, + "license": "MIT", "dependencies": { "@types/methods": "^1.1.4", "@types/superagent": "^8.1.0" @@ -7481,37 +6729,38 @@ "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz", - "integrity": "sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/type-utils": "7.2.0", - "@typescript-eslint/utils": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -7527,47 +6776,21 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/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==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", - "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/typescript-estree": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -7583,16 +6806,17 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", - "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -7600,18 +6824,19 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz", - "integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.2.0", - "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -7627,12 +6852,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", - "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", "dev": true, + "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -7640,22 +6866,23 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", - "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -7667,26 +6894,28 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -7695,21 +6924,19 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz", - "integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/typescript-estree": "7.2.0", - "semver": "^7.5.4" + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -7719,44 +6946,18 @@ "eslint": "^8.56.0" } }, - "node_modules/@typescript-eslint/utils/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==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", - "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.2.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -7767,13 +6968,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/@vitest/coverage-v8": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.4.0.tgz", - "integrity": "sha512-4hDGyH1SvKpgZnIByr9LhGgCEuF9DKM34IBLCC/fVfy24Z3+PZ+Ii9hsVBsHvY1umM1aGPEjceRkzxCfcQ10wg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.0.tgz", + "integrity": "sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.1", "@bcoe/v8-coverage": "^0.2.3", @@ -7787,24 +6990,24 @@ "picocolors": "^1.0.0", "std-env": "^3.5.0", "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.2.0" + "test-exclude": "^6.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.4.0" + "vitest": "1.6.0" } }, "node_modules/@vitest/expect": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz", - "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz", + "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", + "@vitest/spy": "1.6.0", + "@vitest/utils": "1.6.0", "chai": "^4.3.10" }, "funding": { @@ -7812,12 +7015,13 @@ } }, "node_modules/@vitest/runner": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz", - "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz", + "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/utils": "1.4.0", + "@vitest/utils": "1.6.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -7830,6 +7034,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^1.0.0" }, @@ -7841,10 +7046,11 @@ } }, "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "dev": true, + "license": "MIT", "engines": { "node": ">=12.20" }, @@ -7853,10 +7059,11 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz", - "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", + "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", "dev": true, + "license": "MIT", "dependencies": { "magic-string": "^0.30.5", "pathe": "^1.1.1", @@ -7867,10 +7074,11 @@ } }, "node_modules/@vitest/spy": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz", - "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz", + "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==", "dev": true, + "license": "MIT", "dependencies": { "tinyspy": "^2.2.0" }, @@ -7879,10 +7087,11 @@ } }, "node_modules/@vitest/utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz", - "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz", + "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==", "dev": true, + "license": "MIT", "dependencies": { "diff-sequences": "^29.6.3", "estree-walker": "^3.0.3", @@ -7897,13 +7106,15 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.0.3.tgz", "integrity": "sha512-IqnKIDWfXBJkvy/k6tzskWTc2NK3LcqHlb+KHGCrjOCH4jfQckRX0NAiIcC/vIqQkzLYw2r2CTSwAxcrtcD6lA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@whatwg-node/fetch": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.8.8.tgz", "integrity": "sha512-CdcjGC2vdKhc13KKxgsc6/616BQ7ooDIgPeTuAiE8qfCnS0mGzcfCOoZXypQSz73nxI+GWc7ZReIAVhxoE1KCg==", "dev": true, + "license": "MIT", "dependencies": { "@peculiar/webcrypto": "^1.4.0", "@whatwg-node/node-fetch": "^0.3.6", @@ -7917,6 +7128,7 @@ "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.3.6.tgz", "integrity": "sha512-w9wKgDO4C95qnXZRwZTfCmLWqyRnooGjcIwG0wADWjw9/HN0p7dtvtgSvItZtUyNteEvgTrd8QojNEqV6DAGTA==", "dev": true, + "license": "MIT", "dependencies": { "@whatwg-node/events": "^0.0.3", "busboy": "^1.6.0", @@ -7928,12 +7140,27 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -7943,10 +7170,11 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -7959,23 +7187,29 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } }, "node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", "dependencies": { "debug": "^4.3.4" }, @@ -7988,6 +7222,7 @@ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, + "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -8001,6 +7236,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -8012,11 +7248,19 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -8026,6 +7270,7 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -8041,6 +7286,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -8052,6 +7298,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -8061,6 +7308,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -8075,13 +7323,25 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/ansis": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.3.2.tgz", + "integrity": "sha512-cFthbBlt+Oi0i9Pv/j6YdVWJh54CtjGACaMPCIrEV4Ha7HWsIjXDwseYV79TIL0B4+KfSwD5S70PeQDkPUd1rA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=15" + } }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -8093,7 +7353,8 @@ "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC" }, "node_modules/arch": { "version": "2.2.0", @@ -8113,12 +7374,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/are-we-there-yet": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -8131,19 +7395,22 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.5", "is-array-buffer": "^3.0.4" @@ -8158,18 +7425,21 @@ "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" }, "node_modules/array-includes": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", - "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" }, "engines": { @@ -8184,20 +7454,24 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/array.prototype.filter": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", - "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.4.tgz", + "integrity": "sha512-r+mCJ7zXgXElgR4IRC+fkvNCeoaavWBs6EdCso5Tbcf+iEMKzBU/His60lt34WEZ9vlb8wDkZvQGcVI5GwkfoQ==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", "es-array-method-boxes-properly": "^1.0.0", + "es-object-atoms": "^1.0.0", "is-string": "^1.0.7" }, "engines": { @@ -8208,15 +7482,17 @@ } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", - "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", + "es-abstract": "^1.23.2", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" }, "engines": { @@ -8231,6 +7507,7 @@ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -8249,6 +7526,7 @@ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -8267,6 +7545,7 @@ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, + "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.5", @@ -8288,13 +7567,15 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/asn1js": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "pvtsutils": "^1.3.2", "pvutils": "^1.1.3", @@ -8309,6 +7590,7 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -8318,6 +7600,7 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -8326,12 +7609,14 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/async-retry": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", "dependencies": { "retry": "0.13.1" } @@ -8339,13 +7624,15 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "node_modules/auto-bind": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz", "integrity": "sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -8358,6 +7645,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": { "possible-typed-array-names": "^1.0.0" }, @@ -8369,10 +7657,11 @@ } }, "node_modules/aws-sdk-client-mock": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-3.0.1.tgz", - "integrity": "sha512-9VAzJLl8mz99KP9HjOm/93d8vznRRUTpJooPBOunRdUAnVYopCe9xmMuu7eVemu8fQ+w6rP7o5bBK1kAFkB2KQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-4.0.1.tgz", + "integrity": "sha512-yD2mmgy73Xce097G5hIpr1k7j50qzvJ49/+6osGZiCyk4m6cwhb+2x7kKFY1gEMwTzaS8+m8fXv9SB29SkRYyQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/sinon": "^10.0.10", "sinon": "^16.1.3", @@ -8380,12 +7669,15 @@ } }, "node_modules/axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", "dev": true, + "license": "MIT", "dependencies": { - "follow-redirects": "^1.14.8" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/axios-proxy-builder": { @@ -8393,6 +7685,7 @@ "resolved": "https://registry.npmjs.org/axios-proxy-builder/-/axios-proxy-builder-0.1.2.tgz", "integrity": "sha512-6uBVsBZzkB3tCC8iyx59mCjQckhB8+GQrI9Cop8eC7ybIsvs/KtnNgEBfRMSEa7GqK2VBGUzgjNYMdPIfotyPA==", "dev": true, + "license": "MIT", "dependencies": { "tunnel": "^0.0.6" } @@ -8401,13 +7694,15 @@ "version": "7.0.0-beta.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/babel-preset-fbjs": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz", "integrity": "sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==", "dev": true, + "license": "MIT", "dependencies": { "@babel/plugin-proposal-class-properties": "^7.0.0", "@babel/plugin-proposal-object-rest-spread": "^7.0.0", @@ -8444,7 +7739,8 @@ "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==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", @@ -8463,13 +7759,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "@mapbox/node-pre-gyp": "^1.0.11", "node-addon-api": "^5.0.0" @@ -8482,6 +7780,7 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", "engines": { "node": "*" } @@ -8491,6 +7790,7 @@ "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", "dev": true, + "license": "MIT", "dependencies": { "execa": "^0.7.0", "executable": "^4.1.0" @@ -8504,6 +7804,7 @@ "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz", "integrity": "sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==", "dev": true, + "license": "MIT", "dependencies": { "execa": "^5.0.0", "find-versions": "^5.0.0" @@ -8520,6 +7821,7 @@ "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-5.1.0.tgz", "integrity": "sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==", "dev": true, + "license": "MIT", "dependencies": { "bin-version": "^6.0.0", "semver": "^7.5.3", @@ -8532,26 +7834,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bin-version-check/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==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/bin-version-check/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -8564,6 +7852,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -8587,6 +7876,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -8599,6 +7889,7 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -8611,6 +7902,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.0.0" }, @@ -8623,25 +7915,12 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/binary-install": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-1.1.0.tgz", - "integrity": "sha512-rkwNGW+3aQVSZoD0/o3mfPN6Yxh3Id0R/xzTVBVVpGNlVz8EGwusksxRlbk/A5iKTZt9zkMn3qIqmAt3vpfbzg==", - "dev": true, - "dependencies": { - "axios": "^0.26.1", - "rimraf": "^3.0.2", - "tar": "^6.1.11" - }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bl": { @@ -8649,6 +7928,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, + "license": "MIT", "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -8659,6 +7939,7 @@ "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -8682,6 +7963,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -8689,38 +7971,50 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC", + "peer": true }, "node_modules/bowser": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "funding": [ { @@ -8736,11 +8030,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -8754,6 +8049,7 @@ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" } @@ -8777,6 +8073,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -8785,43 +8082,8 @@ "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, - "node_modules/builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", - "dev": true, - "dependencies": { - "semver": "^7.0.0" - } - }, - "node_modules/builtins/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==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/builtins/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" }, "node_modules/busboy": { "version": "1.6.0", @@ -8839,6 +8101,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -8848,6 +8111,7 @@ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -8857,6 +8121,7 @@ "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.6.0" } @@ -8866,6 +8131,7 @@ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", "dev": true, + "license": "MIT", "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -8884,6 +8150,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, + "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -8898,6 +8165,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -8916,13 +8184,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -8932,6 +8202,7 @@ "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", "dev": true, + "license": "MIT", "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" @@ -8942,14 +8213,25 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { - "version": "1.0.30001597", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz", - "integrity": "sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==", + "version": "1.0.30001647", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001647.tgz", + "integrity": "sha512-n83xdNiyeNcHpzWY+1aFbqCK7LuLfBricc4+alSQL2Xb6OR3XpnQAmlDG+pQcdTfiHRuLcQ96VOfrPSGiNJYSg==", "dev": true, "funding": [ { @@ -8964,13 +8246,15 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/capital-case": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", "dev": true, + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -8982,6 +8266,7 @@ "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", "dev": true, + "license": "MIT", "dependencies": { "ansicolors": "~0.3.2", "redeyed": "~2.1.0" @@ -8991,10 +8276,11 @@ } }, "node_modules/chai": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", - "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, + "license": "MIT", "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -9002,16 +8288,27 @@ "get-func-name": "^2.0.2", "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.8" + "type-detect": "^4.1.0" }, "engines": { "node": ">=4" } }, + "node_modules/chai/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -9024,6 +8321,7 @@ "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", "dev": true, + "license": "MIT", "dependencies": { "camel-case": "^4.1.2", "capital-case": "^1.0.4", @@ -9044,6 +8342,7 @@ "resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.15.tgz", "integrity": "sha512-3+GIFhk3sNuvFAJKU46o26OdzudQlPNBCu1ZQi3cMeMHhty1bhDxu2WrEilVNYaGvqUtR1VSigFcJOiS13dRhQ==", "dev": true, + "license": "MIT", "dependencies": { "change-case": "^4.1.2", "is-lower-case": "^2.0.2", @@ -9061,13 +8360,15 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, + "license": "MIT", "dependencies": { "get-func-name": "^2.0.2" }, @@ -9075,11 +8376,54 @@ "node": "*" } }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -9099,31 +8443,28 @@ "fsevents": "~2.3.2" } }, - "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, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", "engines": { "node": ">=10" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "dev": true, + "license": "MIT" + }, "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, + "license": "MIT", "engines": { "node": ">=6" } @@ -9133,6 +8474,7 @@ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, + "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" }, @@ -9145,6 +8487,7 @@ "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", "dev": true, + "license": "MIT", "dependencies": { "string-width": "^4.2.3" }, @@ -9157,6 +8500,7 @@ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -9169,6 +8513,7 @@ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", "dev": true, + "license": "MIT", "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" @@ -9185,6 +8530,7 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true, + "license": "ISC", "engines": { "node": ">= 10" } @@ -9194,6 +8540,7 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -9208,6 +8555,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -9225,6 +8573,7 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8" } @@ -9234,6 +8583,7 @@ "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", "dev": true, + "license": "MIT", "dependencies": { "mimic-response": "^1.0.0" }, @@ -9241,11 +8591,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" @@ -9259,6 +8620,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -9270,13 +8632,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/color-string": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -9286,20 +8650,23 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", "bin": { "color-support": "bin.js" } }, "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 + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -9312,6 +8679,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12" } @@ -9321,6 +8689,7 @@ "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4.0.0" } @@ -9330,6 +8699,7 @@ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -9337,18 +8707,28 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "dev": true, + "license": "MIT" }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" }, "node_modules/console.table": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/console.table/-/console.table-0.10.0.tgz", "integrity": "sha512-dPyZofqggxuvSf7WXvNjuRfnsOk1YazkVP8FdxH4tcH2c37wc79/Yl6Bhr7Lsu00KMgy2ql/qCMuNu8xctZM8g==", "dev": true, + "license": "MIT", "dependencies": { "easy-table": "1.1.0" }, @@ -9361,6 +8741,7 @@ "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", "dev": true, + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -9371,6 +8752,7 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -9382,6 +8764,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9390,12 +8773,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9403,18 +8788,33 @@ "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" }, "node_modules/cookiejar": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", + "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", "dependencies": { "object-assign": "^4", "vary": "^1" @@ -9428,6 +8828,7 @@ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dev": true, + "license": "MIT", "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", @@ -9449,67 +8850,190 @@ } } }, - "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/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, + "license": "MIT" + }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-inspect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.0.tgz", + "integrity": "sha512-4PFfn4b5ZN6FMNGSZlyb7wUhuN8wvj8t/VQHZdM4JsDcruGJ8L2kf9zao98QIrBPFCpdk27qst/AGTl7pL3ypQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" }, - "node_modules/cross-fetch": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", - "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", "dev": true, + "license": "MIT", "dependencies": { - "node-fetch": "^2.6.12" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cross-inspect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.0.tgz", - "integrity": "sha512-4PFfn4b5ZN6FMNGSZlyb7wUhuN8wvj8t/VQHZdM4JsDcruGJ8L2kf9zao98QIrBPFCpdk27qst/AGTl7pL3ypQ==", + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.4.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">=16.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">= 8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/dataloader": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.2.tgz", "integrity": "sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + "version": "1.11.12", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz", + "integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==", + "license": "MIT" }, "node_modules/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -9527,15 +9051,23 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/decko": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decko/-/decko-1.2.0.tgz", + "integrity": "sha512-m8FnyHXV1QX+S1cl+KPFDIl6NMkxtKsy6+U/aYyjrOqWMuwAwYWu7ePqrsUHtDR5Y8Yk2pi/KIDSgF+vT4cPOQ==", + "dev": true + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, + "license": "MIT", "dependencies": { "mimic-response": "^3.1.0" }, @@ -9551,6 +9083,7 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -9559,10 +9092,11 @@ } }, "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, + "license": "MIT", "dependencies": { "type-detect": "^4.0.0" }, @@ -9575,6 +9109,7 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4.0.0" } @@ -9583,13 +9118,15 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, + "license": "MIT", "dependencies": { "clone": "^1.0.2" }, @@ -9602,6 +9139,7 @@ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -9610,6 +9148,7 @@ "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==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -9627,6 +9166,7 @@ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, + "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -9643,6 +9183,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -9650,12 +9191,14 @@ "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -9665,6 +9208,7 @@ "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6.0" } @@ -9673,6 +9217,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -9683,14 +9228,16 @@ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "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": ">=8" } @@ -9700,6 +9247,7 @@ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, + "license": "ISC", "dependencies": { "asap": "^2.0.0", "wrappy": "1" @@ -9710,6 +9258,7 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -9719,6 +9268,7 @@ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -9728,6 +9278,7 @@ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, + "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -9735,11 +9286,20 @@ "node": ">=8" } }, + "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==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -9747,11 +9307,82 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/dompurify": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", + "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==", + "dev": true, + "license": "(MPL-2.0 OR Apache-2.0)" + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dot-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -9762,6 +9393,7 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -9773,6 +9405,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.3.tgz", "integrity": "sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==", + "license": "MIT", "engines": { "node": ">=4" } @@ -9782,6 +9415,7 @@ "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz", "integrity": "sha512-oq33hWOSSnl2Hoh00tZWaIPi1ievrD9aFG82/IgjlycAnW9hHx5PkJiXpxPsgEE+H7BsbVQXFVFST8TEXS6/pA==", "dev": true, + "license": "MIT", "optionalDependencies": { "wcwidth": ">=1.0.1" } @@ -9790,6 +9424,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" } @@ -9797,13 +9432,15 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "jake": "^10.8.5" }, @@ -9815,20 +9452,23 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.708", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.708.tgz", - "integrity": "sha512-iWgEEvREL4GTXXHKohhh33+6Y8XkPI5eHihDmm8zUk5Zo7HICEW+wI/j5kJ2tbuNUCXJ/sNXa03ajW635DiJXA==", - "dev": true + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz", + "integrity": "sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==", + "dev": true, + "license": "ISC" }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -9838,15 +9478,17 @@ "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": { "once": "^1.4.0" } }, "node_modules/enhanced-resolve": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", - "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -9855,32 +9497,102 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/enzyme": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", + "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array.prototype.flat": "^1.2.3", + "cheerio": "^1.0.0-rc.3", + "enzyme-shallow-equal": "^1.0.1", + "function.prototype.name": "^1.1.2", + "has": "^1.0.3", + "html-element-map": "^1.2.0", + "is-boolean-object": "^1.0.1", + "is-callable": "^1.1.5", + "is-number-object": "^1.0.4", + "is-regex": "^1.0.5", + "is-string": "^1.0.5", + "is-subset": "^0.1.1", + "lodash.escape": "^4.0.1", + "lodash.isequal": "^4.5.0", + "object-inspect": "^1.7.0", + "object-is": "^1.0.2", + "object.assign": "^4.1.0", + "object.entries": "^1.1.1", + "object.values": "^1.1.1", + "raf": "^3.4.1", + "rst-selector-parser": "^2.2.3", + "string.prototype.trim": "^1.2.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/enzyme-shallow-equal": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.7.tgz", + "integrity": "sha512-/um0GFqUXnpM9SvKtje+9Tjoz3f1fpBC3eXRFrNs8kpYn69JljciYP7KZTqM/YQbUY9KUjvKB4jo/q+L6WGGvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0", + "object-is": "^1.1.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT" }, "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/es-abstract": { - "version": "1.22.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", - "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", "dev": true, + "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", "arraybuffer.prototype.slice": "^1.0.3", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", "es-define-property": "^1.0.0", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", @@ -9891,10 +9603,11 @@ "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "hasown": "^2.0.1", + "hasown": "^2.0.2", "internal-slot": "^1.0.7", "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.3", @@ -9905,17 +9618,17 @@ "object-keys": "^1.1.1", "object.assign": "^4.1.5", "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.0", + "safe-array-concat": "^1.1.2", "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", - "string.prototype.trimstart": "^1.0.7", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.2", "typed-array-byte-length": "^1.0.1", "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.5", + "typed-array-length": "^1.0.6", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -9928,12 +9641,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -9945,6 +9661,20 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, "engines": { "node": ">= 0.4" } @@ -9954,6 +9684,7 @@ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, + "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.4", "has-tostringtag": "^1.0.2", @@ -9968,6 +9699,7 @@ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, + "license": "MIT", "dependencies": { "hasown": "^2.0.0" } @@ -9977,6 +9709,7 @@ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, + "license": "MIT", "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -9989,12 +9722,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -10002,29 +9743,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -10032,6 +9773,7 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -10039,13 +9781,15 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -10058,6 +9802,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -10113,6 +9858,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, + "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -10125,6 +9871,7 @@ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", @@ -10136,6 +9883,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -10145,6 +9893,7 @@ "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", "dev": true, + "license": "ISC", "dependencies": { "debug": "^4.3.4", "enhanced-resolve": "^5.12.0", @@ -10170,6 +9919,7 @@ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.2.7" }, @@ -10187,6 +9937,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -10196,6 +9947,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", "dev": true, + "license": "MIT", "dependencies": { "eslint-utils": "^2.0.0", "regexpp": "^3.0.0" @@ -10215,6 +9967,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, + "license": "MIT", "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -10246,6 +9999,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10256,6 +10010,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -10265,6 +10020,7 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -10277,6 +10033,7 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.0" }, @@ -10289,6 +10046,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -10301,6 +10059,7 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, + "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -10313,6 +10072,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, + "license": "MIT", "dependencies": { "eslint-plugin-es": "^3.0.0", "eslint-utils": "^2.0.0", @@ -10333,6 +10093,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10343,6 +10104,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -10351,18 +10113,19 @@ } }, "node_modules/eslint-plugin-vitest": { - "version": "0.3.26", - "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.3.26.tgz", - "integrity": "sha512-oxe5JSPgRjco8caVLTh7Ti8PxpwJdhSV0hTQAmkFcNcmy/9DnqLB/oNVRA11RmVRP//2+jIIT6JuBEcpW3obYg==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.5.4.tgz", + "integrity": "sha512-um+odCkccAHU53WdKAw39MY61+1x990uXjSPguUCq3VcEHdqJrOb8OTMrbYlY6f9jAKx7x98kLVlIe3RJeJqoQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^7.1.1" + "@typescript-eslint/utils": "^7.7.1" }, "engines": { "node": "^18.0.0 || >= 20.0.0" }, "peerDependencies": { - "eslint": ">=8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "vitest": "*" }, "peerDependenciesMeta": { @@ -10379,6 +10142,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -10395,6 +10159,7 @@ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^1.1.0" }, @@ -10410,6 +10175,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=4" } @@ -10419,6 +10185,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -10431,6 +10198,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10441,6 +10209,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -10452,11 +10221,25 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/eslint/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -10472,6 +10255,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -10484,6 +10268,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -10496,6 +10281,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -10513,6 +10299,7 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -10522,10 +10309,11 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -10538,6 +10326,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -10550,6 +10339,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -10559,6 +10349,7 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -10568,6 +10359,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -10576,15 +10368,34 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, "node_modules/execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^5.0.1", "get-stream": "^3.0.0", @@ -10603,6 +10414,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", "dev": true, + "license": "MIT", "dependencies": { "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", @@ -10614,6 +10426,7 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, + "license": "ISC", "dependencies": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -10624,6 +10437,7 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^1.0.0" }, @@ -10636,6 +10450,7 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10645,6 +10460,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -10656,13 +10472,15 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/executable": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", "dev": true, + "license": "MIT", "dependencies": { "pify": "^2.2.0" }, @@ -10671,9 +10489,10 @@ } }, "node_modules/expo-server-sdk": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/expo-server-sdk/-/expo-server-sdk-3.7.0.tgz", - "integrity": "sha512-SMZuBiIWejAdMMIOTjGQlprcwvSyLfeUQlooyGB5q6GvZ8zHjp+if8Q4k7xczUBTqIqTzs5IvTZnTiqA9Oe9WA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/expo-server-sdk/-/expo-server-sdk-3.10.0.tgz", + "integrity": "sha512-isymUVz18Syp9G+TPs2MVZ6WdMoyLw8hDLhpywOd8JqM6iGTka6Dr8Dzq7mjGQ8C8486rxLawZx/W+ps+vkjLQ==", + "license": "MIT", "dependencies": { "node-fetch": "^2.6.0", "promise-limit": "^2.7.0", @@ -10684,6 +10503,7 @@ "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -10725,6 +10545,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -10732,13 +10553,15 @@ "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/ext-list": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", "dev": true, + "license": "MIT", "dependencies": { "mime-db": "^1.28.0" }, @@ -10751,6 +10574,7 @@ "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", "dev": true, + "license": "MIT", "dependencies": { "ext-list": "^2.0.0", "sort-keys-length": "^1.0.0" @@ -10762,13 +10586,15 @@ "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "dev": true, + "license": "MIT", "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -10783,6 +10609,7 @@ "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-11.0.0.tgz", "integrity": "sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20 || >= 14.13" }, @@ -10794,19 +10621,22 @@ "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 + "dev": true, + "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==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -10818,35 +10648,26 @@ "node": ">=8.6.0" } }, - "node_modules/fast-glob/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, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "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 + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-querystring": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", "dev": true, + "license": "MIT", "dependencies": { "fast-decode-uri-component": "^1.0.1" } @@ -10855,31 +10676,34 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/fast-url-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", "dev": true, + "license": "MIT", "dependencies": { "punycode": "^1.3.2" } }, "node_modules/fast-xml-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", - "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" } ], + "license": "MIT", "dependencies": { "strnum": "^1.0.5" }, @@ -10892,6 +10716,7 @@ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -10901,6 +10726,7 @@ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" } @@ -10910,6 +10736,7 @@ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", "dev": true, + "license": "MIT", "dependencies": { "cross-fetch": "^3.1.5", "fbjs-css-vars": "^1.0.0", @@ -10924,13 +10751,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -10946,6 +10775,7 @@ "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.8.0" } @@ -10955,6 +10785,7 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, + "license": "MIT", "dependencies": { "flat-cache": "^3.0.4" }, @@ -10967,6 +10798,7 @@ "resolved": "https://registry.npmjs.org/file-type/-/file-type-17.1.6.tgz", "integrity": "sha512-hlDw5Ev+9e883s0pwUsuuYNu4tD7GgpUnOvykjv1Gya0ZIjuKumthDRua90VUn6/nlRKAjcxLUnHNTIUWwWIiw==", "dev": true, + "license": "MIT", "dependencies": { "readable-web-to-node-stream": "^3.0.2", "strtok3": "^7.0.0-alpha.9", @@ -10984,27 +10816,17 @@ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dev": true, + "license": "Apache-2.0", "dependencies": { "minimatch": "^5.0.1" } }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/filename-reserved-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -11017,6 +10839,7 @@ "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-5.1.1.tgz", "integrity": "sha512-M45CbrJLGACfrPOkrTp3j2EcO9OBkKUYME0eiqOCa7i2poaklU0jhlIaMlr8ijLorT0uLAzrn3qXOp5684CkfA==", "dev": true, + "license": "MIT", "dependencies": { "filename-reserved-regex": "^3.0.0", "strip-outer": "^2.0.0", @@ -11030,10 +10853,11 @@ } }, "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==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -11045,6 +10869,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -11062,6 +10887,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -11069,13 +10895,15 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -11092,6 +10920,7 @@ "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", "integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==", "dev": true, + "license": "MIT", "dependencies": { "semver-regex": "^4.0.5" }, @@ -11107,6 +10936,7 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -11120,7 +10950,8 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/follow-redirects": { "version": "1.15.6", @@ -11133,6 +10964,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -11147,14 +10979,23 @@ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, + "license": "MIT", "dependencies": { "is-callable": "^1.1.3" } }, + "node_modules/foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", + "dev": true, + "license": "MIT" + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -11165,15 +11006,15 @@ } }, "node_modules/formidable": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", - "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.1.tgz", + "integrity": "sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==", "dev": true, + "license": "MIT", "dependencies": { "dezalgo": "^1.0.4", "hexoid": "^1.0.0", - "once": "^1.4.0", - "qs": "^6.11.0" + "once": "^1.4.0" }, "funding": { "url": "https://ko-fi.com/tunnckoCore/commissions" @@ -11183,6 +11024,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -11191,6 +11033,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -11200,6 +11043,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -11213,6 +11057,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -11224,6 +11069,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -11234,7 +11080,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", @@ -11242,6 +11089,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -11254,6 +11102,7 @@ "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" } @@ -11263,6 +11112,7 @@ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -11281,6 +11131,7 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11289,6 +11140,8 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", @@ -11305,14 +11158,16 @@ } }, "node_modules/gaxios": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.3.0.tgz", - "integrity": "sha512-p+ggrQw3fBwH2F5N/PAI4k/G/y1art5OxKpb2J2chwNNHM4hHuAOtivjPuirMF4KNKwTTUal/lPfL2+7h2mEcg==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.0.tgz", + "integrity": "sha512-DSrkyMTfAnAm4ks9Go20QGOcXEyW/NmZhvTYBU2rb4afBB393WIMQPWPEDMl/k8xqiNN9HYq2zao3oWXsdl2Tg==", + "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" + "node-fetch": "^2.6.9", + "uuid": "^10.0.0" }, "engines": { "node": ">=14" @@ -11322,6 +11177,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -11333,6 +11189,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "license": "Apache-2.0", "dependencies": { "gaxios": "^6.0.0", "json-bigint": "^1.0.0" @@ -11346,6 +11203,7 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -11355,6 +11213,7 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -11364,6 +11223,7 @@ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -11372,6 +11232,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -11391,15 +11252,24 @@ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.0.0" } }, + "node_modules/get-port-please": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.2.tgz", + "integrity": "sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==", + "dev": true, + "license": "MIT" + }, "node_modules/get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -11409,6 +11279,7 @@ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.5", "es-errors": "^1.3.0", @@ -11422,10 +11293,11 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", - "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", + "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", "dev": true, + "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -11437,6 +11309,8 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -11453,21 +11327,23 @@ } }, "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { - "is-glob": "^4.0.3" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=10.13.0" + "node": ">= 6" } }, "node_modules/glob/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==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11477,6 +11353,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -11489,17 +11366,20 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "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": { - "define-properties": "^1.1.3" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -11513,6 +11393,7 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "license": "MIT", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -11532,12 +11413,14 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/google-auth-library": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.7.0.tgz", - "integrity": "sha512-I/AvzBiUXDzLOy4iIZ2W+Zq33W4lcukQv1nl7C8WUA6SQwyQwUwu3waNmWNAvzds//FG8SZ+DnKnW/2k6mQS8A==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.13.0.tgz", + "integrity": "sha512-p9Y03Uzp/Igcs36zAaB0XTSwZ8Y0/tpYiz5KIde5By+H9DCVUSYtDWZu6aFXsWTqENMb8BD/pDT3hR8NVrPkfA==", + "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", @@ -11554,6 +11437,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "license": "MIT", "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -11566,6 +11450,7 @@ "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", "dev": true, + "license": "MIT", "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", @@ -11590,27 +11475,31 @@ "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 + "dev": true, + "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/graphql": { - "version": "16.8.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", - "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, "node_modules/graphql-config": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-5.0.3.tgz", - "integrity": "sha512-BNGZaoxIBkv9yy6Y7omvsaBUHOzfFcII3UN++tpH8MGOKFPFkCPZuwx09ggANMt8FgyWP1Od8SWPmrUEZca4NQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-5.1.0.tgz", + "integrity": "sha512-g4mNs1OZmZI+LHwRly3BbHO3mRZryyRCbmFKDGsFGde3U0F7TlIwJ0mhX1KTJlQzGQVDZDexZWnvIwodFERPvg==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-tools/graphql-file-loader": "^8.0.0", "@graphql-tools/json-file-loader": "^8.0.0", @@ -11642,6 +11531,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11652,6 +11542,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.3.tgz", "integrity": "sha512-lIUdtK5hdofgCTu3aT0sOaHsYR37viUuIc0rwnnDXImbwFRcumyLMeZaM0t0I/fgxS6s6JMfu0rLD1Wz9pv1ng==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -11664,6 +11555,7 @@ "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-6.1.0.tgz", "integrity": "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==", "dev": true, + "license": "MIT", "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", "cross-fetch": "^3.1.5" @@ -11676,6 +11568,8 @@ "version": "2.12.6", "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", + "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.1.0" }, @@ -11687,10 +11581,11 @@ } }, "node_modules/graphql-ws": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.15.0.tgz", - "integrity": "sha512-xWGAtm3fig9TIhSaNsg0FaDZ8Pyn/3re3RFlP4rhQcmjRDIPpk1EhRuNB+YSJtLzttyuToaDiNhwT1OMoGnJnw==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.16.0.tgz", + "integrity": "sha512-Ju2RCU2dQMgSKtArPbEtsK5gNLnsQyTNIo/T7cZNp96niC1x0KdJNZV0TIoilceBPQwfb5itrGl8pkFeOUMl4A==", "dev": true, + "license": "MIT", "workspaces": [ "website" ], @@ -11705,6 +11600,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" @@ -11713,11 +11609,54 @@ "node": ">=14.0.0" } }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/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, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11727,6 +11666,7 @@ "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" } @@ -11735,6 +11675,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -11746,6 +11687,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -11757,6 +11699,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -11769,6 +11712,7 @@ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -11782,12 +11726,14 @@ "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" }, "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" }, @@ -11800,6 +11746,7 @@ "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", "dev": true, + "license": "MIT", "dependencies": { "capital-case": "^1.0.4", "tslib": "^2.0.3" @@ -11809,6 +11756,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz", "integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==", + "license": "MIT", "engines": { "node": ">=16.0.0" } @@ -11818,68 +11766,86 @@ "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "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 - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "node_modules/http-call": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/http-call/-/http-call-5.3.0.tgz", - "integrity": "sha512-ahwimsC23ICE4kPl9xTBjKB4inbRaeLyZeRunC/1Jy/Z6X8tv22MEAjK+KBOMSVLaqXPTTmd8638waVIKLGx2w==", + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, + "license": "ISC", "dependencies": { - "content-type": "^1.0.4", - "debug": "^4.1.1", - "is-retry-allowed": "^1.1.0", - "is-stream": "^2.0.0", - "parse-json": "^4.0.0", - "tunnel-agent": "^0.6.0" + "lru-cache": "^10.0.1" }, "engines": { - "node": ">=8.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/http-call/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/hosted-git-info/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/html-element-map": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.3.1.tgz", + "integrity": "sha512-6XMlxrAFX4UEEGxctfFnmrFaaZFNf9i5fNuV5wZ3WWQ4FVaNP1aX1LkX9j2mfEx1NpjeE/rL3nmgEn23GdFmrg==", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "peer": true, + "dependencies": { + "array.prototype.filter": "^1.0.0", + "call-bind": "^1.0.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/http-call/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==", + "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, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "peer": true, "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" } }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -11896,6 +11862,7 @@ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -11904,11 +11871,19 @@ "node": ">= 14" } }, + "node_modules/http2-client": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", + "dev": true, + "license": "MIT" + }, "node_modules/http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", "dev": true, + "license": "MIT", "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" @@ -11918,9 +11893,10 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "license": "MIT", "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -11934,6 +11910,7 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=10.17.0" } @@ -11943,6 +11920,7 @@ "resolved": "https://registry.npmjs.org/hyperlinker/-/hyperlinker-1.0.0.tgz", "integrity": "sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -11951,6 +11929,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -11976,13 +11955,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -11991,13 +11972,21 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true + "dev": true, + "license": "ISC" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" }, "node_modules/immutable": { "version": "3.7.6", "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz", "integrity": "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.8.0" } @@ -12007,6 +11996,7 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -12023,6 +12013,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -12032,6 +12023,7 @@ "resolved": "https://registry.npmjs.org/import-from/-/import-from-4.0.0.tgz", "integrity": "sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12.2" }, @@ -12044,6 +12036,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -12053,14 +12046,30 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/index-to-position": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz", + "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -12069,13 +12078,15 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/inquirer": { "version": "8.2.6", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -12102,6 +12113,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -12118,6 +12130,7 @@ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.0", @@ -12127,20 +12140,12 @@ "node": ">= 0.4" } }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dev": true, + "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" } @@ -12149,6 +12154,7 @@ "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" } @@ -12158,6 +12164,7 @@ "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, + "license": "MIT", "dependencies": { "is-relative": "^1.0.0", "is-windows": "^1.0.1" @@ -12171,6 +12178,7 @@ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1" @@ -12186,13 +12194,15 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, + "license": "MIT", "dependencies": { "has-bigints": "^1.0.1" }, @@ -12205,6 +12215,7 @@ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -12217,6 +12228,7 @@ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -12233,6 +12245,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" }, @@ -12241,12 +12254,32 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", "dev": true, + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12257,6 +12290,7 @@ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, + "license": "MIT", "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -12272,6 +12306,7 @@ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, + "license": "MIT", "bin": { "is-docker": "cli.js" }, @@ -12287,6 +12322,7 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -12295,6 +12331,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -12304,6 +12341,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -12316,6 +12354,7 @@ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -12325,6 +12364,7 @@ "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-2.0.2.tgz", "integrity": "sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -12334,6 +12374,7 @@ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -12346,6 +12387,7 @@ "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" } @@ -12355,6 +12397,7 @@ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, + "license": "MIT", "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -12370,6 +12413,7 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -12379,6 +12423,7 @@ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -12388,6 +12433,7 @@ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -12404,6 +12450,7 @@ "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, + "license": "MIT", "dependencies": { "is-unc-path": "^1.0.0" }, @@ -12411,20 +12458,12 @@ "node": ">=0.10.0" } }, - "node_modules/is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-shared-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7" }, @@ -12440,6 +12479,7 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -12449,6 +12489,7 @@ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, + "license": "MIT", "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -12459,11 +12500,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, + "license": "MIT", "dependencies": { "has-symbols": "^1.0.2" }, @@ -12479,6 +12529,7 @@ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, + "license": "MIT", "dependencies": { "which-typed-array": "^1.1.14" }, @@ -12494,6 +12545,7 @@ "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, + "license": "MIT", "dependencies": { "unc-path-regex": "^0.1.2" }, @@ -12506,6 +12558,7 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -12518,6 +12571,7 @@ "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-2.0.2.tgz", "integrity": "sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -12527,6 +12581,7 @@ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.2" }, @@ -12539,6 +12594,7 @@ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -12548,6 +12604,7 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, + "license": "MIT", "dependencies": { "is-docker": "^2.0.0" }, @@ -12559,19 +12616,22 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "dev": true, + "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 + "dev": true, + "license": "ISC" }, "node_modules/isomorphic-ws": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", "dev": true, + "license": "MIT", "peerDependencies": { "ws": "*" } @@ -12581,6 +12641,7 @@ "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": ">=8" } @@ -12590,6 +12651,7 @@ "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": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -12599,23 +12661,12 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/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==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-report/node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -12627,13 +12678,11 @@ } }, "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -12642,10 +12691,11 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.4.tgz", - "integrity": "sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==", + "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, + "license": "BSD-3-Clause", "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", @@ -12660,6 +12710,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" @@ -12669,10 +12720,11 @@ } }, "node_modules/jake": { - "version": "10.8.7", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", - "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", @@ -12691,6 +12743,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -12701,6 +12754,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -12717,6 +12771,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -12728,22 +12783,25 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "dev": true, + "license": "MIT", "bin": { "jiti": "bin/jiti.js" } }, "node_modules/jose": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.3.tgz", - "integrity": "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz", + "integrity": "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -12753,6 +12811,7 @@ "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -12761,13 +12820,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -12780,6 +12841,7 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, @@ -12791,6 +12853,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", "dependencies": { "bignumber.js": "^9.0.0" } @@ -12799,55 +12862,46 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "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 + "dev": true, + "license": "MIT" }, "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-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 + "dev": true, + "license": "MIT" }, - "node_modules/json-stable-stringify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", - "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", + "node_modules/json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", - "isarray": "^2.0.5", - "jsonify": "^0.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "foreach": "^2.0.4" } }, + "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==", + "dev": true, + "license": "MIT" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-templates": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/json-templates/-/json-templates-5.0.0.tgz", - "integrity": "sha512-oQ9FrrwX1GmACI1iXZpUEaM+32NS5K9NtQDeQc6e1N950w/3D5VNjpq/otXwNqdv3DfLK85AdYWQZ6oBwqA1aw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/json-templates/-/json-templates-5.1.0.tgz", + "integrity": "sha512-WIurn7pduz4+HVfXKHlDyNjypO6UuyiMVf/aHScHqp96gl1QK7Nw1XzbFjUxKsDSPImT/Is+eAIbde8ns2EoLw==", "dev": true, + "license": "MIT", "dependencies": { "object-path": "^0.11.8" } @@ -12857,6 +12911,7 @@ "resolved": "https://registry.npmjs.org/json-to-pretty-yaml/-/json-to-pretty-yaml-1.2.2.tgz", "integrity": "sha512-rvm6hunfCcqegwYaG5T4yKJWxc9FXFgBVrcTZ4XfSVRwa5HA/Xs+vB/Eo9treYYHCeNM0nrSUr82V/M31Urc7A==", "dev": true, + "license": "Apache-2.0", "dependencies": { "remedial": "^1.0.7", "remove-trailing-spaces": "^1.0.6" @@ -12870,6 +12925,7 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -12877,17 +12933,12 @@ "node": ">=6" } }, - "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", - "dev": true - }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -12895,19 +12946,11 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", - "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", @@ -12929,6 +12972,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -12938,30 +12982,18 @@ "node_modules/jsonwebtoken/node_modules/jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jsonwebtoken/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==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" } }, "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -12973,12 +13005,14 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -12989,6 +13023,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" @@ -12999,6 +13034,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -13008,6 +13044,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -13016,17 +13053,41 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/listr2": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz", "integrity": "sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==", "dev": true, + "license": "MIT", "dependencies": { "cli-truncate": "^2.1.0", "colorette": "^2.0.16", @@ -13049,11 +13110,19 @@ } } }, + "node_modules/listr2/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, + "license": "MIT" + }, "node_modules/listr2/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -13066,58 +13135,12 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/load-json-file": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", - "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.15", - "parse-json": "^4.0.0", - "pify": "^4.0.1", - "strip-bom": "^3.0.0", - "type-fest": "^0.3.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/load-json-file/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, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/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, - "engines": { - "node": ">=6" - } - }, - "node_modules/load-json-file/node_modules/type-fest": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", - "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/local-pkg": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", "dev": true, + "license": "MIT", "dependencies": { "mlly": "^1.4.2", "pkg-types": "^1.0.3" @@ -13129,11 +13152,21 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -13148,76 +13181,106 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.clone": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", "integrity": "sha512-GhrVeweiTD6uTmmn5hV/lzgCQhccwReIVRLHp7LT4SopOjqEZ5BbX8b5WWEtAKasjmy8hR7ZPwsYlxRCku5odg==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT" }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -13234,6 +13297,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -13250,6 +13314,7 @@ "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^4.3.0", "cli-cursor": "^3.1.0", @@ -13268,6 +13333,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -13284,6 +13350,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", + "license": "MIT", "engines": { "node": ">= 0.6.0" }, @@ -13295,13 +13362,15 @@ "node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, + "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -13314,6 +13383,7 @@ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, + "license": "MIT", "dependencies": { "get-func-name": "^2.0.1" } @@ -13323,6 +13393,7 @@ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -13332,6 +13403,7 @@ "resolved": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-2.0.2.tgz", "integrity": "sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -13341,6 +13413,7 @@ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -13349,37 +13422,45 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", "engines": { "node": ">=12" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, "node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/magicast": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.3.tgz", - "integrity": "sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", + "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", - "source-map-js": "^1.0.2" + "@babel/parser": "^7.24.4", + "@babel/types": "^7.24.0", + "source-map-js": "^1.2.0" } }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", "dependencies": { "semver": "^6.0.0" }, @@ -13394,21 +13475,44 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "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": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13416,19 +13520,22 @@ "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "license": "MIT" }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -13438,6 +13545,7 @@ "resolved": "https://registry.npmjs.org/meros/-/meros-1.3.0.tgz", "integrity": "sha512-2BNGOimxEz5hmjUG2FwoxCt5HN7BXdaWyFqEwxPTrJzVdABtrL4TiHTcsWSFAxPQ/tOnEaQEJh3qWq71QRMY+w==", "dev": true, + "license": "MIT", "engines": { "node": ">=13" }, @@ -13454,17 +13562,19 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -13475,6 +13585,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -13483,9 +13594,11 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13494,6 +13607,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -13501,11 +13615,21 @@ "node": ">= 0.6" } }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -13515,23 +13639,22 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10" } }, "node_modules/minimist": { @@ -13539,6 +13662,7 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -13547,6 +13671,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", "engines": { "node": ">=8" } @@ -13555,6 +13680,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -13567,6 +13693,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -13578,6 +13705,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -13586,35 +13714,110 @@ } }, "node_modules/mlly": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz", - "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", + "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", "dev": true, + "license": "MIT", "dependencies": { "acorn": "^8.11.3", "pathe": "^1.1.2", - "pkg-types": "^1.0.3", - "ufo": "^1.3.2" + "pkg-types": "^1.1.1", + "ufo": "^1.5.3" } }, "node_modules/mnemonist": { "version": "0.38.3", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", + "license": "MIT", "dependencies": { "obliterator": "^1.6.1" } }, + "node_modules/mobx": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.13.1.tgz", + "integrity": "sha512-ekLRxgjWJr8hVxj9ZKuClPwM/iHckx3euIJ3Np7zLVNtqJvfbbq7l370W/98C8EabdQ1pB5Jd3BbDWxJPNnaOg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-react": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-9.1.1.tgz", + "integrity": "sha512-gVV7AdSrAAxqXOJ2bAbGa5TkPqvITSzaPiiEkzpW4rRsMhSec7C2NBCJYILADHKp2tzOAIETGRsIY0UaCV5aEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mobx-react-lite": "^4.0.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/mobx-react-lite": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.0.7.tgz", + "integrity": "sha512-RjwdseshK9Mg8On5tyJZHtGD+J78ZnCnRaxeQDSiciKVQDUbfZcXhmld0VMxAwvcTnPEHZySGGewm467Fcpreg==", + "dev": true, + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, "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==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/nanoid": { "version": "3.3.7", @@ -13627,6 +13830,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -13638,31 +13842,74 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/natural-orderby": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-2.0.3.tgz", "integrity": "sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q==", "dev": true, + "license": "MIT", "engines": { "node": "*" } }, + "node_modules/nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.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/nearley/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==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "!win32" @@ -13677,6 +13924,7 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true, + "license": "MIT", "optional": true }, "node_modules/nise": { @@ -13684,6 +13932,7 @@ "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0", "@sinonjs/fake-timers": "^11.2.2", @@ -13697,21 +13946,24 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "node_modules/nise/node_modules/path-to-regexp": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", - "dev": true + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", + "dev": true, + "license": "MIT" }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "dev": true, + "license": "MIT", "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" @@ -13720,17 +13972,20 @@ "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", - "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "license": "MIT" }, "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -13746,11 +14001,25 @@ } } }, + "node_modules/node-fetch-h2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", + "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "http2-client": "^1.2.5" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/node-gyp-build": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", - "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", + "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", "dev": true, + "license": "MIT", "optional": true, "bin": { "node-gyp-build": "bin.js", @@ -13762,19 +14031,32 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/node-readfiles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", + "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es6-promise": "^3.2.1" + } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" }, "node_modules/nodemon": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", - "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", "dev": true, + "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", @@ -13803,6 +14085,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -13813,27 +14096,17 @@ "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" } }, - "node_modules/nodemon/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==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/nodemon/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -13842,13 +14115,11 @@ } }, "node_modules/nodemon/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -13861,6 +14132,7 @@ "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" }, @@ -13872,6 +14144,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", "dependencies": { "abbrev": "1" }, @@ -13887,6 +14160,7 @@ "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" } @@ -13896,6 +14170,7 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -13904,9 +14179,9 @@ } }, "node_modules/npm": { - "version": "9.8.1", - "resolved": "https://registry.npmjs.org/npm/-/npm-9.8.1.tgz", - "integrity": "sha512-AfDvThQzsIXhYgk9zhbk5R+lh811lKkLAeQMMhSypf1BM7zUafeIIBzMzespeuVEJ0+LvY36oRQYf7IKLzU3rw==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.8.2.tgz", + "integrity": "sha512-x/AIjFIKRllrhcb48dqUNAAZl0ig9+qMuN91RpZo3Cb2+zuibfh+KISl6+kVVyktDz230JKc208UkQwwMqyB+w==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -13915,15 +14190,15 @@ "@npmcli/map-workspaces", "@npmcli/package-json", "@npmcli/promise-spawn", + "@npmcli/redact", "@npmcli/run-script", + "@sigstore/tuf", "abbrev", "archy", "cacache", "chalk", "ci-info", "cli-columns", - "cli-table3", - "columnify", "fastest-levenshtein", "fs-minipass", "glob", @@ -13951,6 +14226,7 @@ "ms", "node-gyp", "nopt", + "normalize-package-data", "npm-audit-report", "npm-install-checks", "npm-package-arg", @@ -13958,7 +14234,6 @@ "npm-profile", "npm-registry-fetch", "npm-user-validate", - "npmlog", "p-map", "pacote", "parse-conflict-json", @@ -13966,7 +14241,7 @@ "qrcode-terminal", "read", "semver", - "sigstore", + "spdx-expression-parse", "ssri", "supports-color", "tar", @@ -13978,6 +14253,7 @@ "write-file-atomic" ], "dev": true, + "license": "Artistic-2.0", "workspaces": [ "docs", "smoke-tests", @@ -13987,72 +14263,72 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^6.3.0", - "@npmcli/config": "^6.2.1", - "@npmcli/fs": "^3.1.0", - "@npmcli/map-workspaces": "^3.0.4", - "@npmcli/package-json": "^4.0.1", - "@npmcli/promise-spawn": "^6.0.2", - "@npmcli/run-script": "^6.0.2", + "@npmcli/arborist": "^7.5.4", + "@npmcli/config": "^8.3.4", + "@npmcli/fs": "^3.1.1", + "@npmcli/map-workspaces": "^3.0.6", + "@npmcli/package-json": "^5.2.0", + "@npmcli/promise-spawn": "^7.0.2", + "@npmcli/redact": "^2.0.1", + "@npmcli/run-script": "^8.1.0", + "@sigstore/tuf": "^2.3.4", "abbrev": "^2.0.0", "archy": "~1.0.0", - "cacache": "^17.1.3", + "cacache": "^18.0.3", "chalk": "^5.3.0", - "ci-info": "^3.8.0", + "ci-info": "^4.0.0", "cli-columns": "^4.0.0", - "cli-table3": "^0.6.3", - "columnify": "^1.6.0", "fastest-levenshtein": "^1.0.16", - "fs-minipass": "^3.0.2", - "glob": "^10.2.7", + "fs-minipass": "^3.0.3", + "glob": "^10.4.2", "graceful-fs": "^4.2.11", - "hosted-git-info": "^6.1.1", - "ini": "^4.1.1", - "init-package-json": "^5.0.0", - "is-cidr": "^4.0.2", - "json-parse-even-better-errors": "^3.0.0", - "libnpmaccess": "^7.0.2", - "libnpmdiff": "^5.0.19", - "libnpmexec": "^6.0.3", - "libnpmfund": "^4.0.19", - "libnpmhook": "^9.0.3", - "libnpmorg": "^5.0.4", - "libnpmpack": "^5.0.19", - "libnpmpublish": "^7.5.0", - "libnpmsearch": "^6.0.2", - "libnpmteam": "^5.0.3", - "libnpmversion": "^4.0.2", - "make-fetch-happen": "^11.1.1", - "minimatch": "^9.0.3", - "minipass": "^5.0.0", + "hosted-git-info": "^7.0.2", + "ini": "^4.1.3", + "init-package-json": "^6.0.3", + "is-cidr": "^5.1.0", + "json-parse-even-better-errors": "^3.0.2", + "libnpmaccess": "^8.0.6", + "libnpmdiff": "^6.1.4", + "libnpmexec": "^8.1.3", + "libnpmfund": "^5.0.12", + "libnpmhook": "^10.0.5", + "libnpmorg": "^6.0.6", + "libnpmpack": "^7.0.4", + "libnpmpublish": "^9.0.9", + "libnpmsearch": "^7.0.6", + "libnpmteam": "^6.0.5", + "libnpmversion": "^6.0.3", + "make-fetch-happen": "^13.0.1", + "minimatch": "^9.0.5", + "minipass": "^7.1.1", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^9.4.0", - "nopt": "^7.2.0", + "node-gyp": "^10.1.0", + "nopt": "^7.2.1", + "normalize-package-data": "^6.0.2", "npm-audit-report": "^5.0.0", - "npm-install-checks": "^6.1.1", - "npm-package-arg": "^10.1.0", - "npm-pick-manifest": "^8.0.1", - "npm-profile": "^7.0.1", - "npm-registry-fetch": "^14.0.5", - "npm-user-validate": "^2.0.0", - "npmlog": "^7.0.1", + "npm-install-checks": "^6.3.0", + "npm-package-arg": "^11.0.2", + "npm-pick-manifest": "^9.1.0", + "npm-profile": "^10.0.0", + "npm-registry-fetch": "^17.1.0", + "npm-user-validate": "^2.0.1", "p-map": "^4.0.0", - "pacote": "^15.2.0", + "pacote": "^18.0.6", "parse-conflict-json": "^3.0.1", - "proc-log": "^3.0.0", + "proc-log": "^4.2.0", "qrcode-terminal": "^0.12.0", - "read": "^2.1.0", - "semver": "^7.5.4", - "sigstore": "^1.7.0", - "ssri": "^10.0.4", + "read": "^3.0.1", + "semver": "^7.6.2", + "spdx-expression-parse": "^4.0.0", + "ssri": "^10.0.6", "supports-color": "^9.4.0", - "tar": "^6.1.15", + "tar": "^6.2.1", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", "treeverse": "^3.0.0", - "validate-npm-package-name": "^5.0.0", - "which": "^3.0.1", + "validate-npm-package-name": "^5.0.1", + "which": "^4.0.0", "write-file-atomic": "^5.0.1" }, "bin": { @@ -14060,7 +14336,36 @@ "npx": "bin/npx-cli.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/npm-run-path": { @@ -14068,6 +14373,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^2.0.0" }, @@ -14080,18 +14386,9 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/@colors/colors": { - "version": "1.5.0", - "dev": true, - "inBundle": true, "license": "MIT", - "optional": true, "engines": { - "node": ">=0.1.90" + "node": ">=4" } }, "node_modules/npm/node_modules/@isaacs/cliui": { @@ -14167,43 +14464,61 @@ "inBundle": true, "license": "ISC" }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "2.2.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "6.3.0", + "version": "7.5.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^3.1.0", - "@npmcli/installed-package-contents": "^2.0.2", + "@npmcli/fs": "^3.1.1", + "@npmcli/installed-package-contents": "^2.1.0", "@npmcli/map-workspaces": "^3.0.2", - "@npmcli/metavuln-calculator": "^5.0.0", + "@npmcli/metavuln-calculator": "^7.1.1", "@npmcli/name-from-folder": "^2.0.0", "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^4.0.0", - "@npmcli/query": "^3.0.0", - "@npmcli/run-script": "^6.0.0", - "bin-links": "^4.0.1", - "cacache": "^17.0.4", + "@npmcli/package-json": "^5.1.0", + "@npmcli/query": "^3.1.0", + "@npmcli/redact": "^2.0.0", + "@npmcli/run-script": "^8.1.0", + "bin-links": "^4.0.4", + "cacache": "^18.0.3", "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^6.1.1", - "json-parse-even-better-errors": "^3.0.0", + "hosted-git-info": "^7.0.2", + "json-parse-even-better-errors": "^3.0.2", "json-stringify-nice": "^1.1.4", - "minimatch": "^9.0.0", - "nopt": "^7.0.0", - "npm-install-checks": "^6.0.0", - "npm-package-arg": "^10.1.0", - "npm-pick-manifest": "^8.0.1", - "npm-registry-fetch": "^14.0.3", - "npmlog": "^7.0.1", - "pacote": "^15.0.8", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^7.2.1", + "npm-install-checks": "^6.2.0", + "npm-package-arg": "^11.0.2", + "npm-pick-manifest": "^9.0.1", + "npm-registry-fetch": "^17.0.1", + "pacote": "^18.0.6", "parse-conflict-json": "^3.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.2.0", + "proggy": "^2.0.0", "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^1.0.2", + "promise-call-limit": "^3.0.1", "read-package-json-fast": "^3.0.2", "semver": "^7.3.7", - "ssri": "^10.0.1", + "ssri": "^10.0.6", "treeverse": "^3.0.0", "walk-up-path": "^3.0.1" }, @@ -14211,42 +14526,30 @@ "arborist": "bin/index.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "6.2.1", + "version": "8.3.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "@npmcli/map-workspaces": "^3.0.2", - "ci-info": "^3.8.0", - "ini": "^4.1.0", - "nopt": "^7.0.0", - "proc-log": "^3.0.0", - "read-package-json-fast": "^3.0.2", + "@npmcli/package-json": "^5.1.1", + "ci-info": "^4.0.0", + "ini": "^4.1.2", + "nopt": "^7.2.1", + "proc-log": "^4.2.0", "semver": "^7.3.5", "walk-up-path": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/disparity-colors": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "ansi-styles": "^4.3.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@npmcli/fs": { - "version": "3.1.0", + "version": "3.1.1", "dev": true, "inBundle": true, "license": "ISC", @@ -14258,26 +14561,27 @@ } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "4.1.0", + "version": "5.0.8", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^6.0.0", - "lru-cache": "^7.4.4", - "npm-pick-manifest": "^8.0.0", - "proc-log": "^3.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", "promise-inflight": "^1.0.1", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^3.0.0" + "which": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "2.0.2", + "version": "2.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -14286,14 +14590,14 @@ "npm-normalize-package-bin": "^3.0.0" }, "bin": { - "installed-package-contents": "lib/index.js" + "installed-package-contents": "bin/index.js" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "3.0.4", + "version": "3.0.6", "dev": true, "inBundle": true, "license": "ISC", @@ -14308,18 +14612,19 @@ } }, "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "5.0.1", + "version": "7.1.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "cacache": "^17.0.0", + "cacache": "^18.0.0", "json-parse-even-better-errors": "^3.0.0", - "pacote": "^15.0.0", + "pacote": "^18.0.0", + "proc-log": "^4.1.0", "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@npmcli/name-from-folder": { @@ -14341,37 +14646,37 @@ } }, "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "4.0.1", + "version": "5.2.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^4.1.0", + "@npmcli/git": "^5.0.0", "glob": "^10.2.2", - "hosted-git-info": "^6.1.1", + "hosted-git-info": "^7.0.0", "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^5.0.0", - "proc-log": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", "semver": "^7.5.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "6.0.2", + "version": "7.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "which": "^3.0.0" + "which": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@npmcli/query": { - "version": "3.0.0", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -14382,20 +14687,30 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "6.0.2", + "version": "8.1.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "@npmcli/node-gyp": "^3.0.0", - "@npmcli/promise-spawn": "^6.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^3.0.0", - "which": "^3.0.0" + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", + "which": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@pkgjs/parseargs": { @@ -14408,104 +14723,121 @@ "node": ">=14" } }, - "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.1.0", + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "2.3.2", "dev": true, "inBundle": true, "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "1.0.2", + "node_modules/npm/node_modules/@sigstore/core": { + "version": "1.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.1.0", - "tuf-js": "^1.1.7" - }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/@tootallnate/once": { - "version": "2.0.0", + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.3.2", "dev": true, "inBundle": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">= 10" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/@tufjs/canonical-json": { - "version": "1.0.0", + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "2.3.2", "dev": true, "inBundle": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/@tufjs/models": { - "version": "1.0.4", + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "2.3.4", "dev": true, "inBundle": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@tufjs/canonical-json": "1.0.0", - "minimatch": "^9.0.0" + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/abbrev": { - "version": "2.0.0", + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "1.2.1", "dev": true, "inBundle": true, - "license": "ISC", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.2" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/abort-controller": { - "version": "3.0.0", + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, "engines": { - "node": ">=6.5" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/agent-base": { - "version": "6.0.2", + "node_modules/npm/node_modules/@tufjs/models": { + "version": "2.0.1", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "debug": "4" + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.4" }, "engines": { - "node": ">= 6.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/agentkeepalive": { - "version": "4.3.0", + "node_modules/npm/node_modules/abbrev": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.1", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "debug": "^4.1.0", - "depd": "^2.0.0", - "humanize-ms": "^1.2.1" + "debug": "^4.3.4" }, "engines": { - "node": ">= 8.0.0" + "node": ">= 14" } }, "node_modules/npm/node_modules/aggregate-error": { @@ -14531,73 +14863,37 @@ } }, "node_modules/npm/node_modules/ansi-styles": { - "version": "4.3.0", + "version": "6.2.1", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/npm/node_modules/aproba": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/archy": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/are-we-there-yet": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^4.1.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" }, - "node_modules/npm/node_modules/balanced-match": { - "version": "1.0.2", + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", "dev": true, "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/base64-js": { - "version": "1.5.1", + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", "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" - } - ], "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/bin-links": { - "version": "4.0.2", + "version": "4.0.4", "dev": true, "inBundle": true, "license": "ISC", @@ -14612,12 +14908,15 @@ } }, "node_modules/npm/node_modules/binary-extensions": { - "version": "2.2.0", + "version": "2.3.0", "dev": true, "inBundle": true, "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm/node_modules/brace-expansion": { @@ -14629,41 +14928,8 @@ "balanced-match": "^1.0.0" } }, - "node_modules/npm/node_modules/buffer": { - "version": "6.0.3", - "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" - } - ], - "inBundle": true, - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/npm/node_modules/builtins": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "semver": "^7.0.0" - } - }, "node_modules/npm/node_modules/cacache": { - "version": "17.1.3", + "version": "18.0.3", "dev": true, "inBundle": true, "license": "ISC", @@ -14671,9 +14937,9 @@ "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-collect": "^1.0.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^4.0.0", @@ -14682,7 +14948,7 @@ "unique-filename": "^3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/chalk": { @@ -14707,7 +14973,7 @@ } }, "node_modules/npm/node_modules/ci-info": { - "version": "3.8.0", + "version": "4.0.0", "dev": true, "funding": [ { @@ -14722,15 +14988,15 @@ } }, "node_modules/npm/node_modules/cidr-regex": { - "version": "3.1.1", + "version": "4.1.1", "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "ip-regex": "^4.1.0" + "ip-regex": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" } }, "node_modules/npm/node_modules/clean-stack": { @@ -14755,32 +15021,8 @@ "node": ">= 10" } }, - "node_modules/npm/node_modules/cli-table3": { - "version": "0.6.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/npm/node_modules/clone": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/npm/node_modules/cmd-shim": { - "version": "6.0.1", + "version": "6.0.3", "dev": true, "inBundle": true, "license": "ISC", @@ -14806,46 +15048,12 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/color-support": { - "version": "1.1.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/npm/node_modules/columnify": { - "version": "1.6.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "strip-ansi": "^6.0.1", - "wcwidth": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/npm/node_modules/common-ancestor-path": { "version": "1.0.1", "dev": true, "inBundle": true, "license": "ISC" }, - "node_modules/npm/node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/console-control-strings": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/cross-spawn": { "version": "7.0.3", "dev": true, @@ -14888,7 +15096,7 @@ } }, "node_modules/npm/node_modules/debug": { - "version": "4.3.4", + "version": "4.3.5", "dev": true, "inBundle": true, "license": "MIT", @@ -14910,35 +15118,8 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/defaults": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/delegates": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/depd": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/npm/node_modules/diff": { - "version": "5.1.0", + "version": "5.2.0", "dev": true, "inBundle": true, "license": "BSD-3-Clause", @@ -14983,24 +15164,6 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/event-target-shim": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/events": { - "version": "3.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/npm/node_modules/exponential-backoff": { "version": "3.1.1", "dev": true, @@ -15017,7 +15180,7 @@ } }, "node_modules/npm/node_modules/foreground-child": { - "version": "3.1.1", + "version": "3.2.1", "dev": true, "inBundle": true, "license": "ISC", @@ -15033,65 +15196,35 @@ } }, "node_modules/npm/node_modules/fs-minipass": { - "version": "3.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^5.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/fs.realpath": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/function-bind": { - "version": "1.1.1", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/gauge": { - "version": "5.0.1", + "version": "3.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^4.0.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" + "minipass": "^7.0.3" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/npm/node_modules/glob": { - "version": "10.2.7", + "version": "10.4.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.0.3", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2", - "path-scurry": "^1.7.0" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { - "glob": "dist/cjs/src/bin.js" + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -15103,34 +15236,16 @@ "inBundle": true, "license": "ISC" }, - "node_modules/npm/node_modules/has": { - "version": "1.0.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/npm/node_modules/has-unicode": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/hosted-git-info": { - "version": "6.1.1", + "version": "7.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "lru-cache": "^7.5.1" + "lru-cache": "^10.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/http-cache-semantics": { @@ -15140,76 +15255,46 @@ "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/http-proxy-agent": { - "version": "5.0.0", + "version": "7.0.2", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/npm/node_modules/https-proxy-agent": { - "version": "5.0.1", + "version": "7.0.5", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/npm/node_modules/humanize-ms": { - "version": "1.2.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" + "node": ">= 14" } }, "node_modules/npm/node_modules/iconv-lite": { "version": "0.6.3", "dev": true, "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/ieee754": { - "version": "1.2.1", - "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" - } - ], - "inBundle": true, - "license": "BSD-3-Clause" + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } }, "node_modules/npm/node_modules/ignore-walk": { - "version": "6.0.3", + "version": "6.0.5", "dev": true, "inBundle": true, "license": "ISC", @@ -15238,24 +15323,8 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/inflight": { - "version": "1.0.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/npm/node_modules/inherits": { - "version": "2.0.4", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/ini": { - "version": "4.1.1", + "version": "4.1.3", "dev": true, "inBundle": true, "license": "ISC", @@ -15264,60 +15333,58 @@ } }, "node_modules/npm/node_modules/init-package-json": { - "version": "5.0.0", + "version": "6.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-package-arg": "^10.0.0", + "@npmcli/package-json": "^5.0.0", + "npm-package-arg": "^11.0.0", "promzard": "^1.0.0", - "read": "^2.0.0", - "read-package-json": "^6.0.0", + "read": "^3.0.1", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/ip": { - "version": "2.0.0", + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } }, "node_modules/npm/node_modules/ip-regex": { - "version": "4.3.0", + "version": "5.0.0", "dev": true, "inBundle": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm/node_modules/is-cidr": { - "version": "4.0.2", + "version": "5.1.0", "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "cidr-regex": "^3.1.1" + "cidr-regex": "^4.1.1" }, "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/is-core-module": { - "version": "2.12.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=14" } }, "node_modules/npm/node_modules/is-fullwidth-code-point": { @@ -15342,7 +15409,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/jackspeak": { - "version": "2.2.1", + "version": "3.4.0", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", @@ -15359,8 +15426,14 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "3.0.0", + "version": "3.0.2", "dev": true, "inBundle": true, "license": "MIT", @@ -15399,210 +15472,205 @@ "license": "MIT" }, "node_modules/npm/node_modules/libnpmaccess": { - "version": "7.0.2", + "version": "8.0.6", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-package-arg": "^10.1.0", - "npm-registry-fetch": "^14.0.3" + "npm-package-arg": "^11.0.2", + "npm-registry-fetch": "^17.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "5.0.19", + "version": "6.1.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^6.3.0", - "@npmcli/disparity-colors": "^3.0.0", - "@npmcli/installed-package-contents": "^2.0.2", - "binary-extensions": "^2.2.0", + "@npmcli/arborist": "^7.5.4", + "@npmcli/installed-package-contents": "^2.1.0", + "binary-extensions": "^2.3.0", "diff": "^5.1.0", - "minimatch": "^9.0.0", - "npm-package-arg": "^10.1.0", - "pacote": "^15.0.8", - "tar": "^6.1.13" + "minimatch": "^9.0.4", + "npm-package-arg": "^11.0.2", + "pacote": "^18.0.6", + "tar": "^6.2.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "6.0.3", + "version": "8.1.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^6.3.0", - "@npmcli/run-script": "^6.0.0", - "ci-info": "^3.7.1", - "npm-package-arg": "^10.1.0", - "npmlog": "^7.0.1", - "pacote": "^15.0.8", - "proc-log": "^3.0.0", - "read": "^2.0.0", + "@npmcli/arborist": "^7.5.4", + "@npmcli/run-script": "^8.1.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^11.0.2", + "pacote": "^18.0.6", + "proc-log": "^4.2.0", + "read": "^3.0.1", "read-package-json-fast": "^3.0.2", "semver": "^7.3.7", "walk-up-path": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "4.0.19", + "version": "5.0.12", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^6.3.0" + "@npmcli/arborist": "^7.5.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmhook": { - "version": "9.0.3", + "version": "10.0.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^14.0.3" + "npm-registry-fetch": "^17.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmorg": { - "version": "5.0.4", + "version": "6.0.6", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^14.0.3" + "npm-registry-fetch": "^17.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "5.0.19", + "version": "7.0.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^6.3.0", - "@npmcli/run-script": "^6.0.0", - "npm-package-arg": "^10.1.0", - "pacote": "^15.0.8" + "@npmcli/arborist": "^7.5.4", + "@npmcli/run-script": "^8.1.0", + "npm-package-arg": "^11.0.2", + "pacote": "^18.0.6" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmpublish": { - "version": "7.5.0", + "version": "9.0.9", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "ci-info": "^3.6.1", - "normalize-package-data": "^5.0.0", - "npm-package-arg": "^10.1.0", - "npm-registry-fetch": "^14.0.3", - "proc-log": "^3.0.0", + "ci-info": "^4.0.0", + "normalize-package-data": "^6.0.1", + "npm-package-arg": "^11.0.2", + "npm-registry-fetch": "^17.0.1", + "proc-log": "^4.2.0", "semver": "^7.3.7", - "sigstore": "^1.4.0", - "ssri": "^10.0.1" + "sigstore": "^2.2.0", + "ssri": "^10.0.6" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmsearch": { - "version": "6.0.2", + "version": "7.0.6", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^14.0.3" + "npm-registry-fetch": "^17.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmteam": { - "version": "5.0.3", + "version": "6.0.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^14.0.3" + "npm-registry-fetch": "^17.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmversion": { - "version": "4.0.2", + "version": "6.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^4.0.1", - "@npmcli/run-script": "^6.0.0", - "json-parse-even-better-errors": "^3.0.0", - "proc-log": "^3.0.0", + "@npmcli/git": "^5.0.7", + "@npmcli/run-script": "^8.1.0", + "json-parse-even-better-errors": "^3.0.2", + "proc-log": "^4.2.0", "semver": "^7.3.7" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/lru-cache": { - "version": "7.18.3", + "version": "10.2.2", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": ">=12" + "node": "14 || >=16.14" } }, "node_modules/npm/node_modules/make-fetch-happen": { - "version": "11.1.1", + "version": "13.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", + "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", + "proc-log": "^4.2.0", "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", "ssri": "^10.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/minimatch": { - "version": "9.0.3", + "version": "9.0.5", "dev": true, "inBundle": true, "license": "ISC", @@ -15617,45 +15685,33 @@ } }, "node_modules/npm/node_modules/minipass": { - "version": "5.0.0", + "version": "7.1.2", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/npm/node_modules/minipass-collect": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minipass-collect/node_modules/minipass": { - "version": "3.3.6", + "version": "2.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.0.3" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/npm/node_modules/minipass-fetch": { - "version": "3.0.3", + "version": "3.0.5", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^5.0.0", + "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^2.1.2" }, @@ -15690,28 +15746,6 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/minipass-json-stream": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "node_modules/npm/node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", "dev": true, @@ -15812,187 +15846,50 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/negotiator": { - "version": "0.6.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/npm/node_modules/node-gyp": { - "version": "9.4.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^11.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^12.13 || ^14.13 || >=16" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/abbrev": { - "version": "1.1.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/node-gyp/node_modules/are-we-there-yet": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/gauge": { - "version": "4.0.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/nopt": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "^1.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/npmlog": { - "version": "6.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 0.6" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/readable-stream": { - "version": "3.6.2", + "node_modules/npm/node_modules/node-gyp": { + "version": "10.1.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": ">= 6" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/signal-exit": { - "version": "3.0.7", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/node-gyp/node_modules/which": { - "version": "2.0.2", + "node_modules/npm/node_modules/node-gyp/node_modules/proc-log": { + "version": "3.0.0", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, "engines": { - "node": ">= 8" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/npm/node_modules/nopt": { - "version": "7.2.0", + "version": "7.2.1", "dev": true, "inBundle": true, "license": "ISC", @@ -16007,18 +15904,17 @@ } }, "node_modules/npm/node_modules/normalize-package-data": { - "version": "5.0.0", + "version": "6.0.2", "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "hosted-git-info": "^6.0.0", - "is-core-module": "^2.8.1", + "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/npm-audit-report": { @@ -16031,7 +15927,7 @@ } }, "node_modules/npm/node_modules/npm-bundled": { - "version": "3.0.0", + "version": "3.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -16043,7 +15939,7 @@ } }, "node_modules/npm/node_modules/npm-install-checks": { - "version": "6.1.1", + "version": "6.3.0", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -16064,80 +15960,81 @@ } }, "node_modules/npm/node_modules/npm-package-arg": { - "version": "10.1.0", + "version": "11.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/npm-packlist": { - "version": "7.0.4", + "version": "8.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "ignore-walk": "^6.0.0" + "ignore-walk": "^6.0.4" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "8.0.1", + "version": "9.1.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "npm-install-checks": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^10.0.0", + "npm-package-arg": "^11.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/npm-profile": { - "version": "7.0.1", + "version": "10.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^14.0.0", - "proc-log": "^3.0.0" + "npm-registry-fetch": "^17.0.1", + "proc-log": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=18.0.0" } }, "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "14.0.5", + "version": "17.1.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "make-fetch-happen": "^11.0.0", - "minipass": "^5.0.0", + "@npmcli/redact": "^2.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", "minizlib": "^2.1.2", - "npm-package-arg": "^10.0.0", - "proc-log": "^3.0.0" + "npm-package-arg": "^11.0.0", + "proc-log": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/npm-user-validate": { - "version": "2.0.0", + "version": "2.0.1", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -16145,30 +16042,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/npmlog": { - "version": "7.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^4.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^5.0.0", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/once": { - "version": "1.4.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/npm/node_modules/p-map": { "version": "4.0.0", "dev": true, @@ -16184,36 +16057,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0" + }, "node_modules/npm/node_modules/pacote": { - "version": "15.2.0", + "version": "18.0.6", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^4.0.0", + "@npmcli/git": "^5.0.0", "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^6.0.1", - "@npmcli/run-script": "^6.0.0", - "cacache": "^17.0.0", + "@npmcli/package-json": "^5.1.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^8.0.0", + "cacache": "^18.0.0", "fs-minipass": "^3.0.0", - "minipass": "^5.0.0", - "npm-package-arg": "^10.0.0", - "npm-packlist": "^7.0.0", - "npm-pick-manifest": "^8.0.0", - "npm-registry-fetch": "^14.0.0", - "proc-log": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", "promise-retry": "^2.0.1", - "read-package-json": "^6.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^1.3.0", + "sigstore": "^2.2.0", "ssri": "^10.0.0", "tar": "^6.1.11" }, "bin": { - "pacote": "lib/bin.js" + "pacote": "bin/index.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/parse-conflict-json": { @@ -16230,15 +16108,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/path-is-absolute": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/npm/node_modules/path-key": { "version": "3.1.1", "dev": true, @@ -16249,32 +16118,23 @@ } }, "node_modules/npm/node_modules/path-scurry": { - "version": "1.9.2", + "version": "1.11.1", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^9.1.1", - "minipass": "^5.0.0 || ^6.0.2" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/path-scurry/node_modules/lru-cache": { - "version": "9.1.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "14 || >=16.14" - } - }, "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "6.0.13", + "version": "6.1.0", "dev": true, "inBundle": true, "license": "MIT", @@ -16287,7 +16147,7 @@ } }, "node_modules/npm/node_modules/proc-log": { - "version": "3.0.0", + "version": "4.2.0", "dev": true, "inBundle": true, "license": "ISC", @@ -16295,13 +16155,13 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/process": { - "version": "0.11.10", + "node_modules/npm/node_modules/proggy": { + "version": "2.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">= 0.6.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/npm/node_modules/promise-all-reject-late": { @@ -16314,7 +16174,7 @@ } }, "node_modules/npm/node_modules/promise-call-limit": { - "version": "1.0.2", + "version": "3.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -16342,12 +16202,12 @@ } }, "node_modules/npm/node_modules/promzard": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "read": "^2.0.0" + "read": "^3.0.1" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -16362,12 +16222,12 @@ } }, "node_modules/npm/node_modules/read": { - "version": "2.1.0", + "version": "3.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "mute-stream": "~1.0.0" + "mute-stream": "^1.0.0" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -16382,21 +16242,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/read-package-json": { - "version": "6.0.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^5.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/npm/node_modules/read-package-json-fast": { "version": "3.0.2", "dev": true, @@ -16410,21 +16255,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/readable-stream": { - "version": "4.4.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/npm/node_modules/retry": { "version": "0.12.0", "dev": true, @@ -16434,83 +16264,6 @@ "node": ">= 4" } }, - "node_modules/npm/node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npm/node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/safe-buffer": { - "version": "5.2.1", - "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" - } - ], - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", "dev": true, @@ -16519,13 +16272,10 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.5.4", + "version": "7.6.2", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -16533,24 +16283,6 @@ "node": ">=10" } }, - "node_modules/npm/node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/set-blocking": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/shebang-command": { "version": "2.0.0", "dev": true, @@ -16573,7 +16305,7 @@ } }, "node_modules/npm/node_modules/signal-exit": { - "version": "4.0.2", + "version": "4.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -16585,20 +16317,20 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "1.7.0", + "version": "2.3.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.1.0", - "@sigstore/tuf": "^1.0.1", - "make-fetch-happen": "^11.0.1" - }, - "bin": { - "sigstore": "bin/sigstore.js" + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/smart-buffer": { @@ -16612,31 +16344,31 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.7.1", + "version": "2.8.3", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "7.0.0", + "version": "8.0.4", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" }, "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/npm/node_modules/spdx-correct": { @@ -16649,14 +16381,24 @@ "spdx-license-ids": "^3.0.0" } }, + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.3.0", + "version": "2.5.0", "dev": true, "inBundle": true, "license": "CC-BY-3.0" }, "node_modules/npm/node_modules/spdx-expression-parse": { - "version": "3.0.1", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "MIT", @@ -16666,32 +16408,29 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.13", + "version": "3.0.18", "dev": true, "inBundle": true, "license": "CC0-1.0" }, + "node_modules/npm/node_modules/sprintf-js": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause" + }, "node_modules/npm/node_modules/ssri": { - "version": "10.0.4", + "version": "10.0.6", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "minipass": "^5.0.0" + "minipass": "^7.0.3" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/string_decoder": { - "version": "1.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/npm/node_modules/string-width": { "version": "4.2.3", "dev": true, @@ -16759,7 +16498,7 @@ } }, "node_modules/npm/node_modules/tar": { - "version": "6.1.15", + "version": "6.2.1", "dev": true, "inBundle": true, "license": "ISC", @@ -16799,6 +16538,15 @@ "node": ">=8" } }, + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, "node_modules/npm/node_modules/text-table": { "version": "0.2.0", "dev": true, @@ -16821,17 +16569,17 @@ } }, "node_modules/npm/node_modules/tuf-js": { - "version": "1.1.7", + "version": "2.2.1", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "@tufjs/models": "1.0.4", + "@tufjs/models": "2.0.1", "debug": "^4.3.4", - "make-fetch-happen": "^11.1.1" + "make-fetch-happen": "^13.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/unique-filename": { @@ -16874,14 +16622,21 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "5.0.0", + "version": "5.0.1", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "builtins": "^5.0.0" - }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -16892,37 +16647,28 @@ "inBundle": true, "license": "ISC" }, - "node_modules/npm/node_modules/wcwidth": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, "node_modules/npm/node_modules/which": { - "version": "3.0.1", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/wide-align": { - "version": "1.1.5", + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" + "engines": { + "node": ">=16" } }, "node_modules/npm/node_modules/wrap-ansi": { @@ -16960,20 +16706,23 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, "inBundle": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", "dev": true, "inBundle": true, "license": "MIT", @@ -16981,7 +16730,7 @@ "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { @@ -17022,12 +16771,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/npm/node_modules/wrappy": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/write-file-atomic": { "version": "5.0.1", "dev": true, @@ -17051,6 +16794,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", @@ -17058,24 +16803,166 @@ "set-blocking": "^2.0.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nullthrows": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/oas-kit-common": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", + "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "fast-safe-stringify": "^2.0.7" + } + }, + "node_modules/oas-linter": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", + "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@exodus/schemasafe": "^1.0.0-rc.2", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-linter/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/oas-resolver": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", + "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "resolve": "resolve.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-resolver/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/oas-schema-walker": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", + "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", + "dev": true, + "license": "BSD-3-Clause", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-validator": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", + "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "call-me-maybe": "^1.0.1", + "oas-kit-common": "^1.0.8", + "oas-linter": "^3.2.2", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "reftools": "^1.1.9", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-validator/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -17085,6 +16972,7 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -17094,6 +16982,7 @@ "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz", "integrity": "sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.12.0" } @@ -17103,6 +16992,7 @@ "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10" } @@ -17112,6 +17002,7 @@ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.5", "define-properties": "^1.2.1", @@ -17125,15 +17016,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/object.fromentries": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", - "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -17143,27 +17052,30 @@ } }, "node_modules/object.groupby": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", - "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, + "license": "MIT", "dependencies": { - "array.prototype.filter": "^1.0.3", - "call-bind": "^1.0.5", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.0.0" + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/object.values": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", - "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -17175,12 +17087,14 @@ "node_modules/obliterator": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", - "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==" + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", + "license": "MIT" }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -17192,6 +17106,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -17201,6 +17116,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -17211,21 +17127,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/openapi-sampler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.5.1.tgz", + "integrity": "sha512-tIWIrZUKNAsbqf3bd9U1oH6JEXo8LNYuDlXw26By67EygpjT+ArFnsxxyTMjFWRfbqo5ozkvgSQDK69Gd8CddA==", "dev": true, - "peer": true + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.7", + "json-pointer": "0.6.2" + } }, "node_modules/openapi-typescript": { - "version": "7.0.0-next.8", - "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.0.0-next.8.tgz", - "integrity": "sha512-823w02orLlqw82og/uTZL/DVXJmLO8gyDEK7JaPv4aQpTOGKwvd3PiQkuB0akJvOHZSJ8OxKYfeilCT1I1fBlw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.3.0.tgz", + "integrity": "sha512-EkljRjYWOPwGXiK++uI9MkGv2Y7uhbkZbi9V1z3r3EpmWVO6aFTHXSLNvxIWo6UT6LCTYgEYkUB3BWQjwwXthg==", "dev": true, + "license": "MIT", "dependencies": { - "@redocly/openapi-core": "^1.10.3", + "@redocly/openapi-core": "^1.16.0", "ansi-colors": "^4.1.3", + "parse-json": "^8.1.0", "supports-color": "^9.4.0", "yargs-parser": "^21.1.1" }, @@ -17236,11 +17158,30 @@ "typescript": "^5.x" } }, + "node_modules/openapi-typescript/node_modules/parse-json": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz", + "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "index-to-position": "^0.1.2", + "type-fest": "^4.7.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/openapi-typescript/node_modules/supports-color": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -17249,17 +17190,18 @@ } }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -17270,6 +17212,7 @@ "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dev": true, + "license": "MIT", "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -17293,6 +17236,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -17309,6 +17253,7 @@ "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", "dev": true, + "license": "MIT", "dependencies": { "arch": "^2.1.0" }, @@ -17321,6 +17266,7 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -17330,6 +17276,7 @@ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -17339,6 +17286,7 @@ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -17348,6 +17296,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -17363,6 +17312,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -17378,6 +17328,7 @@ "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, + "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" }, @@ -17393,6 +17344,7 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -17402,6 +17354,7 @@ "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", "dev": true, + "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -17412,6 +17365,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -17424,6 +17378,7 @@ "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", "dev": true, + "license": "MIT", "dependencies": { "is-absolute": "^1.0.0", "map-cache": "^0.2.0", @@ -17438,6 +17393,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -17451,10 +17407,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -17464,6 +17450,7 @@ "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", "dev": true, + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -17474,16 +17461,25 @@ "resolved": "https://registry.npmjs.org/password-prompt/-/password-prompt-1.1.3.tgz", "integrity": "sha512-HkrjG2aJlvF0t2BMH0e2LB/EHf3Lcq3fNMzy4GYHcQblAvOl+QQji1Lx7WRBMqpVK8p+KR7bCg7oqAMXtdgqyw==", "dev": true, + "license": "0BSD", "dependencies": { "ansi-escapes": "^4.3.2", "cross-spawn": "^7.0.3" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "node_modules/path-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", "dev": true, + "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -17494,6 +17490,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -17502,6 +17499,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -17511,6 +17509,7 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -17519,13 +17518,15 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/path-root": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", "dev": true, + "license": "MIT", "dependencies": { "path-root-regex": "^0.1.0" }, @@ -17538,6 +17539,7 @@ "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -17545,13 +17547,15 @@ "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "license": "MIT" }, "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, + "license": "MIT", "engines": { "node": ">=8" } @@ -17560,22 +17564,25 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } }, "node_modules/peek-readable": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz", - "integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.1.3.tgz", + "integrity": "sha512-kCsc9HwH5RgVA3H3VqkWFyGQwsxUxLdiSX1d5nqAm7hnMFjNFX1VhBLmJoUY0hZNc8gmDNgBkLjfhiWPsziXWA==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -17584,17 +17591,34 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/perfect-scrollbar": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", + "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -17607,28 +17631,31 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/piscina": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.4.0.tgz", - "integrity": "sha512-+AQduEJefrOApE4bV7KRmp3N2JnnyErlVqq4P/jmko4FPz9Z877BCccl/iB3FdrWSUkvbGV9Kan/KllJgat3Vg==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.6.1.tgz", + "integrity": "sha512-z30AwWGtQE+Apr+2WBZensP2lIvwoaMcOPkQlIEmSGMJNUvaYACylPYrQM6wSdUNJlnDVMSpLv7xTMJqlVshOA==", "dev": true, + "license": "MIT", "optionalDependencies": { "nice-napi": "^1.0.2" } }, "node_modules/pkg-types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", - "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.3.tgz", + "integrity": "sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==", "dev": true, + "license": "MIT", "dependencies": { - "jsonc-parser": "^3.2.0", - "mlly": "^1.2.0", - "pathe": "^1.1.0" + "confbox": "^0.1.7", + "mlly": "^1.7.1", + "pathe": "^1.1.2" } }, "node_modules/pluralize": { @@ -17636,23 +17663,38 @@ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/polished": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", + "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -17668,29 +17710,39 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -17706,6 +17758,7 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -17720,6 +17773,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -17727,11 +17781,32 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "dev": true, + "license": "MIT", "dependencies": { "asap": "~2.0.3" } @@ -17739,12 +17814,14 @@ "node_modules/promise-limit": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", - "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==" + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", + "license": "ISC" }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -17757,14 +17834,35 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", "engines": { "node": ">= 4" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, "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" @@ -17773,23 +17871,33 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", - "dev": true + "dev": true, + "license": "ISC" }, "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 + "dev": true, + "license": "MIT" }, "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, + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -17799,13 +17907,15 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pvtsutils": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.6.1" } @@ -17815,6 +17925,7 @@ "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -17823,6 +17934,7 @@ "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.4" }, @@ -17851,13 +17963,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -17865,10 +17979,55 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "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==", + "dev": true, + "license": "CC0-1.0", + "peer": true + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/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==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -17877,6 +18036,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -17887,16 +18047,73 @@ "node": ">= 0.8" } }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-tabs": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.0.2.tgz", + "integrity": "sha512-aQXTKolnM28k3KguGDBSAbJvcowOQr23A+CUJdzJtOSDOtTwzEaJA+1U4KwhNL9+Obe+jFS7geuvA7ICQPXOnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": "^18.0.0" + } }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -17911,6 +18128,7 @@ "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", "dev": true, + "license": "MIT", "dependencies": { "readable-stream": "^3.6.0" }, @@ -17927,6 +18145,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -17934,38 +18153,81 @@ "node": ">=8.10.0" } }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/redeyed": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", "dev": true, + "license": "MIT", "dependencies": { "esprima": "~4.0.0" } }, + "node_modules/redoc": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.1.5.tgz", + "integrity": "sha512-POSbVg+7WLf+/5/c6GWLxL7+9t2D+1WlZdLN0a6qaCQc+ih3XYzteRBkXEN5kjrYrRNjdspfxTZkDLN5WV3Tzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cfaester/enzyme-adapter-react-18": "^0.8.0", + "@redocly/openapi-core": "^1.4.0", + "classnames": "^2.3.2", + "decko": "^1.2.0", + "dompurify": "^3.0.6", + "eventemitter3": "^5.0.1", + "json-pointer": "^0.6.2", + "lunr": "^2.3.9", + "mark.js": "^8.11.1", + "marked": "^4.3.0", + "mobx-react": "^9.1.1", + "openapi-sampler": "^1.5.0", + "path-browserify": "^1.0.1", + "perfect-scrollbar": "^1.5.5", + "polished": "^4.2.2", + "prismjs": "^1.29.0", + "prop-types": "^15.8.1", + "react-tabs": "^6.0.2", + "slugify": "~1.4.7", + "stickyfill": "^1.1.1", + "swagger2openapi": "^7.0.8", + "url-template": "^2.0.8" + }, + "engines": { + "node": ">=6.9", + "npm": ">=3.0.0" + }, + "peerDependencies": { + "core-js": "^3.1.4", + "mobx": "^6.0.4", + "react": "^16.8.4 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0", + "styled-components": "^4.1.1 || ^5.1.1 || ^6.0.5" + } + }, + "node_modules/reftools": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", + "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", + "dev": true, + "license": "BSD-3-Clause", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.6", "define-properties": "^1.2.1", @@ -17984,6 +18246,7 @@ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -17996,6 +18259,7 @@ "resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-12.0.0.tgz", "integrity": "sha512-QU6JKr1tMsry22DXNy9Whsq5rmvwr3LSZiiWV/9+DFpuTWvp+WFhobWMc8TC4OjKFfNhEZy7mOiqUAn5atQtug==", "dev": true, + "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", "fbjs": "^3.0.0", @@ -18007,6 +18271,7 @@ "resolved": "https://registry.npmjs.org/remedial/-/remedial-1.0.8.tgz", "integrity": "sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg==", "dev": true, + "license": "(MIT OR Apache-2.0)", "engines": { "node": "*" } @@ -18015,19 +18280,22 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/remove-trailing-spaces": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/remove-trailing-spaces/-/remove-trailing-spaces-1.0.8.tgz", "integrity": "sha512-O3vsMYfWighyFbTd8hk8VaSj9UAGENxAtX+//ugIst2RMk5e03h6RoIS+0ylsFxY1gvmPuAY/PO4It+gPEeySA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -18037,6 +18305,7 @@ "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==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -18045,13 +18314,15 @@ "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 + "dev": true, + "license": "ISC" }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, + "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -18068,13 +18339,15 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -18084,6 +18357,7 @@ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -18093,6 +18367,7 @@ "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", "dev": true, + "license": "MIT", "dependencies": { "lowercase-keys": "^2.0.0" }, @@ -18105,6 +18380,7 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, + "license": "MIT", "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -18113,10 +18389,22 @@ "node": ">=8" } }, + "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, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.12" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", "engines": { "node": ">= 4" } @@ -18126,21 +18414,25 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", - "dev": true + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "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==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -18152,10 +18444,11 @@ } }, "node_modules/rollup": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", - "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", + "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "1.0.5" }, @@ -18167,27 +18460,43 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.0", - "@rollup/rollup-android-arm64": "4.13.0", - "@rollup/rollup-darwin-arm64": "4.13.0", - "@rollup/rollup-darwin-x64": "4.13.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-arm64-musl": "4.13.0", - "@rollup/rollup-linux-riscv64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-musl": "4.13.0", - "@rollup/rollup-win32-arm64-msvc": "4.13.0", - "@rollup/rollup-win32-ia32-msvc": "4.13.0", - "@rollup/rollup-win32-x64-msvc": "4.13.0", + "@rollup/rollup-android-arm-eabi": "4.20.0", + "@rollup/rollup-android-arm64": "4.20.0", + "@rollup/rollup-darwin-arm64": "4.20.0", + "@rollup/rollup-darwin-x64": "4.20.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", + "@rollup/rollup-linux-arm-musleabihf": "4.20.0", + "@rollup/rollup-linux-arm64-gnu": "4.20.0", + "@rollup/rollup-linux-arm64-musl": "4.20.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", + "@rollup/rollup-linux-riscv64-gnu": "4.20.0", + "@rollup/rollup-linux-s390x-gnu": "4.20.0", + "@rollup/rollup-linux-x64-gnu": "4.20.0", + "@rollup/rollup-linux-x64-musl": "4.20.0", + "@rollup/rollup-win32-arm64-msvc": "4.20.0", + "@rollup/rollup-win32-ia32-msvc": "4.20.0", + "@rollup/rollup-win32-x64-msvc": "4.20.0", "fsevents": "~2.3.2" } }, + "node_modules/rst-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", + "integrity": "sha512-nDG1rZeP6oFTLN6yNDV/uiAvs1+FS/KlrEwh7+y7dpuApDBy6bI2HTBcc0/V8lv9OTqfyD34eF7au2pm8aBbhA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "lodash.flattendeep": "^4.4.0", + "nearley": "^2.7.10" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -18211,6 +18520,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -18220,6 +18530,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -18229,6 +18540,7 @@ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "get-intrinsic": "^1.2.4", @@ -18259,13 +18571,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/safe-regex-test": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.6", "es-errors": "^1.3.0", @@ -18281,18 +18595,31 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } }, "node_modules/scuid": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/scuid/-/scuid-1.1.0.tgz", "integrity": "sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -18302,6 +18629,7 @@ "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -18314,6 +18642,7 @@ "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz", "integrity": "sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.3.5" }, @@ -18324,26 +18653,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semver-truncate/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==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/semver-truncate/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -18355,6 +18670,7 @@ "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -18378,6 +18694,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -18385,18 +18702,21 @@ "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": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/send/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==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/sentence-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", "dev": true, + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -18407,6 +18727,7 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "license": "MIT", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -18420,12 +18741,14 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" }, "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==", + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -18443,6 +18766,7 @@ "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": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -18457,17 +18781,20 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -18476,11 +18803,19 @@ "sha.js": "bin.js" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true, + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -18493,6 +18828,7 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -18502,31 +18838,76 @@ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "node_modules/should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", "dev": true, + "license": "MIT", "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "node_modules/should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "should-type": "^1.4.0" + } + }, + "node_modules/should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "node_modules/should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" } }, + "node_modules/should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "dev": true, + "license": "MIT" + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -18544,24 +18925,28 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" }, "node_modules/signedsource": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/signedsource/-/signedsource-1.0.0.tgz", "integrity": "sha512-6+eerH9fEnNmi/hyM1DXcRK3pWdoMQtlkQ+ns0ntzunjKqp5i3sKCc80ym8Fib3iaYhdJUOPdhlJWj1tvge2Ww==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "dev": true, + "license": "MIT", "dependencies": { "is-arrayish": "^0.3.1" } @@ -18570,13 +18955,15 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true + "dev": true, + "license": "MIT" }, "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": { "semver": "^7.5.3" }, @@ -18584,31 +18971,67 @@ "node": ">=10" } }, - "node_modules/simple-update-notifier/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/simple-update-notifier/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { "node": ">=10" } }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "node_modules/simple-websocket": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", + "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", "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": { - "lru-cache": "^6.0.0" + "debug": "^4.3.1", + "queue-microtask": "^1.2.2", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0", + "ws": "^7.4.2" + } + }, + "node_modules/simple-websocket/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" }, - "bin": { - "semver": "bin/semver.js" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" }, - "engines": { - "node": ">=10" + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/sinon": { @@ -18616,6 +19039,7 @@ "resolved": "https://registry.npmjs.org/sinon/-/sinon-16.1.3.tgz", "integrity": "sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0", "@sinonjs/fake-timers": "^10.3.0", @@ -18634,6 +19058,7 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -18643,6 +19068,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -18652,11 +19078,22 @@ "node": ">=8" } }, + "node_modules/slugify": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.4.7.tgz", + "integrity": "sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/snake-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", "dev": true, + "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -18667,6 +19104,7 @@ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", "dev": true, + "license": "MIT", "dependencies": { "is-plain-obj": "^1.0.0" }, @@ -18679,6 +19117,7 @@ "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", "dev": true, + "license": "MIT", "dependencies": { "sort-keys": "^1.0.0" }, @@ -18691,15 +19130,17 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">= 8" } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -18709,6 +19150,7 @@ "resolved": "https://registry.npmjs.org/sponge-case/-/sponge-case-1.0.1.tgz", "integrity": "sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -18717,18 +19159,21 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "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 + "dev": true, + "license": "MIT" }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -18737,6 +19182,13 @@ "version": "3.7.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/stickyfill": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stickyfill/-/stickyfill-1.1.1.tgz", + "integrity": "sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA==", "dev": true }, "node_modules/streamsearch": { @@ -18752,6 +19204,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } @@ -18760,12 +19213,14 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz", "integrity": "sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -18776,14 +19231,16 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -18793,28 +19250,33 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "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": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.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" @@ -18824,6 +19286,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -18836,6 +19299,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -18845,6 +19309,7 @@ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -18854,6 +19319,7 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -18863,6 +19329,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -18871,28 +19338,31 @@ } }, "node_modules/strip-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz", - "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", "dev": true, + "license": "MIT", "dependencies": { - "js-tokens": "^8.0.2" + "js-tokens": "^9.0.0" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/strip-literal/node_modules/js-tokens": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz", - "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", - "dev": true + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", + "dev": true, + "license": "MIT" }, "node_modules/strip-outer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-2.0.0.tgz", "integrity": "sha512-A21Xsm1XzUkK0qK1ZrytDUvqsQWict2Cykhvi0fBQntGG5JSprESasEyV1EZ/4CiR5WB5KjzLTrP/bO37B0wPg==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -18904,6 +19374,7 @@ "version": "10.17.0", "resolved": "https://registry.npmjs.org/stripe/-/stripe-10.17.0.tgz", "integrity": "sha512-JHV2KoL+nMQRXu3m9ervCZZvi4DDCJfzHUE6CmtJxR9TmizyYfrVuhGvnsZLLnheby9Qrnf4Hq6iOEcejGwnGQ==", + "license": "MIT", "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" @@ -18912,59 +19383,102 @@ "node": "^8.1 || >=10.*" } }, + "node_modules/stripe-event-types": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/stripe-event-types/-/stripe-event-types-3.1.0.tgz", + "integrity": "sha512-jhPNX5FL81QiBFadek3h+PBQKU5Xg3NURTia6tzyqieDStESbvKqOhnA29ECdA1lPB8KSB5/xdK20k6Q1yOejQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "stripe": ">=10.0.0" + } + }, "node_modules/strnum": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "license": "MIT" }, "node_modules/strtok3": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz", - "integrity": "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.1.1.tgz", + "integrity": "sha512-mKX8HA/cdBqMKUr0MMZAFssCkIGoZeSCMXgnt79yKxNFguMLVFgRe6wB+fsL0NmoHDbeyZXczy7vEPSoo3rkzg==", "dev": true, + "license": "MIT", "dependencies": { "@tokenizer/token": "^0.3.0", - "peek-readable": "^5.0.0" + "peek-readable": "^5.1.3" }, "engines": { - "node": ">=14.16" + "node": ">=16" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/styled-components": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.12.tgz", + "integrity": "sha512-n/O4PzRPhbYI0k1vKKayfti3C/IGcPf+DqcrOB7O/ab9x4u/zjqraneT5N45+sIe87cxrCApXM8Bna7NYxwoTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.38", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true, + "license": "0BSD" + }, + "node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "dev": true, + "license": "MIT" + }, "node_modules/superagent": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", - "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", + "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", "dev": true, + "license": "MIT", "dependencies": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.4", "debug": "^4.3.4", "fast-safe-stringify": "^2.1.1", "form-data": "^4.0.0", - "formidable": "^2.1.2", + "formidable": "^3.5.1", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.0", - "semver": "^7.3.8" - }, - "engines": { - "node": ">=6.4.0 <13 || >=14" - } - }, - "node_modules/superagent/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==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" + "qs": "^6.11.0" }, "engines": { - "node": ">=10" + "node": ">=14.18.0" } }, "node_modules/superagent/node_modules/mime": { @@ -18972,6 +19486,7 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -18979,32 +19494,18 @@ "node": ">=4.0.0" } }, - "node_modules/superagent/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/supertest": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.4.tgz", - "integrity": "sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", + "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", "dev": true, + "license": "MIT", "dependencies": { "methods": "^1.1.2", - "superagent": "^8.1.2" + "superagent": "^9.0.1" }, "engines": { - "node": ">=6.4.0" + "node": ">=14.18.0" } }, "node_modules/supports-color": { @@ -19012,6 +19513,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -19024,6 +19526,7 @@ "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" @@ -19037,6 +19540,7 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -19044,19 +19548,42 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/swagger-cli": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/swagger-cli/-/swagger-cli-4.0.4.tgz", - "integrity": "sha512-Cp8YYuLny3RJFQ4CvOBTaqmOOgYsem52dPx1xM5S4EUWFblIh2Q8atppMZvXKUr1e9xH5RwipYpmdUzdPcxWcA==", + "node_modules/swagger2openapi": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", + "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@apidevtools/swagger-cli": "4.0.4" + "call-me-maybe": "^1.0.1", + "node-fetch": "^2.6.1", + "node-fetch-h2": "^2.3.0", + "node-readfiles": "^0.2.0", + "oas-kit-common": "^1.0.8", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "oas-validator": "^5.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" }, "bin": { - "swagger-cli": "swagger-cli.js" + "boast": "boast.js", + "oas-validate": "oas-validate.js", + "swagger2openapi": "swagger2openapi.js" }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/swagger2openapi/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=10" + "node": ">= 6" } }, "node_modules/swaggerhub-cli": { @@ -19065,6 +19592,7 @@ "integrity": "sha512-cVbT8Aezolsd3Pkqr+fHCHEw5+jUzHV6M53BwnNqZj13w2BZoLzcQ4jgcUxKSbADyim0Eg74Xf3ih9+FiIgs9A==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@oclif/core": "^3.3.1", "@oclif/plugin-help": "^6.0.3", @@ -19084,26 +19612,12 @@ "node": ">=16.0.0" } }, - "node_modules/swaggerhub-cli/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==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/swaggerhub-cli/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -19116,6 +19630,7 @@ "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-2.0.2.tgz", "integrity": "sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -19125,14 +19640,16 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -19150,6 +19667,7 @@ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, + "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -19164,6 +19682,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -19174,6 +19693,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -19185,25 +19705,29 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/tinybench": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz", - "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==", - "dev": true + "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/tinypool": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", - "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -19213,6 +19737,7 @@ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -19222,6 +19747,7 @@ "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", "integrity": "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -19231,6 +19757,7 @@ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, + "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -19243,6 +19770,7 @@ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -19252,6 +19780,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -19263,6 +19792,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -19272,6 +19802,7 @@ "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", "dev": true, + "license": "MIT", "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" @@ -19285,42 +19816,27 @@ } }, "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, - "dependencies": { - "nopt": "~1.0.10" - }, + "license": "ISC", "bin": { "nodetouch": "bin/nodetouch.js" } }, - "node_modules/touch/node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, "node_modules/trim-repeated": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-2.0.0.tgz", "integrity": "sha512-QUHBFTJGdOwmp0tbOG505xAgOp/YliZP/6UgafFXYZ26WT1bvQmSMJUvkeVSASuJJHbqsFbynTvkd5W8RBTipg==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^5.0.0" }, @@ -19333,6 +19849,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -19345,6 +19862,7 @@ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=16" }, @@ -19356,13 +19874,15 @@ "version": "2.2.5", "resolved": "https://registry.npmjs.org/ts-log/-/ts-log-2.2.5.tgz", "integrity": "sha512-PGcnJoTBnVGy6yYNFxWVNkdcAuAMstvutN9MgDJIV6L0oG8fB+ZNNy1T+wJzah8RPGor1mZuPQkVfXNDpy9eHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -19406,15 +19926,17 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } }, "node_modules/tsconfck": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.0.3.tgz", - "integrity": "sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.1.tgz", + "integrity": "sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==", "dev": true, + "license": "MIT", "bin": { "tsconfck": "bin/tsconfck.js" }, @@ -19435,6 +19957,7 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, + "license": "MIT", "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", @@ -19445,36 +19968,27 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -19487,15 +20001,17 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/type-fest": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.12.0.tgz", - "integrity": "sha512-5Y2/pp2wtJk8o08G0CMkuFPCO354FGwk/vbidxrdhRGZfd0tFnb4Qb8anp9XxXriwBgVPjdWbKpGl4J9lJY2jQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.23.0.tgz", + "integrity": "sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" }, @@ -19507,6 +20023,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -19520,6 +20037,7 @@ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -19534,6 +20052,7 @@ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -19553,6 +20072,7 @@ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dev": true, + "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", @@ -19569,10 +20089,11 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", - "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -19589,10 +20110,11 @@ } }, "node_modules/typescript": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", - "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -19602,16 +20124,18 @@ } }, "node_modules/typescript-eslint": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.2.0.tgz", - "integrity": "sha512-VqXEBqzPxJlR8Lfg2Dywe4XpIk637kwp2sfMQ+vudNHo48TUvnlHzAyFMQknv0AdhvZFXQN0a0t9SPI3rsAYew==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.18.0.tgz", + "integrity": "sha512-PonBkP603E3tt05lDkbOMyaxJjvKqQrXsnow72sVeOFINDE/qNmnnd+f9b4N+U7W6MXnnYyrhtmF2t08QWwUbA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "7.2.0", - "@typescript-eslint/parser": "7.2.0" + "@typescript-eslint/eslint-plugin": "7.18.0", + "@typescript-eslint/parser": "7.18.0", + "@typescript-eslint/utils": "7.18.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -19627,9 +20151,9 @@ } }, "node_modules/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "dev": true, "funding": [ { @@ -19645,21 +20169,38 @@ "url": "https://github.com/sponsors/faisalman" } ], + "license": "MIT", "engines": { "node": "*" } }, "node_modules/ufo": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.1.tgz", - "integrity": "sha512-HGyF79+/qZ4soRvM+nHERR2pJ3VXDZ/8sL1uLahdgEDf580NkgiWOxLk33FetExqOWp352JZRsgXbG/4MaGOSg==", - "dev": true + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.19.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.1.tgz", + "integrity": "sha512-y/2wiW+ceTYR2TSSptAhfnEtpLaQ4Ups5zrjB2d3kuVxHj16j/QJwPl5PvuGy9uARb39J0+iKxcRPvtpsx4A4A==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -19675,6 +20216,7 @@ "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -19683,13 +20225,15 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/undici": { "version": "5.28.4", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "dev": true, + "license": "MIT", "dependencies": { "@fastify/busboy": "^2.0.0" }, @@ -19700,13 +20244,15 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -19716,6 +20262,7 @@ "resolved": "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz", "integrity": "sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==", "dev": true, + "license": "MIT", "dependencies": { "normalize-path": "^2.1.1" }, @@ -19728,6 +20275,7 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", "dev": true, + "license": "MIT", "dependencies": { "remove-trailing-separator": "^1.0.1" }, @@ -19739,14 +20287,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, "funding": [ { @@ -19762,9 +20311,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -19778,6 +20328,7 @@ "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -19787,6 +20338,7 @@ "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -19796,6 +20348,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -19805,37 +20358,59 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "dev": true, + "license": "BSD" + }, "node_modules/urlpattern-polyfill": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz", "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -19844,30 +20419,15 @@ "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 - }, - "node_modules/v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } + "license": "MIT" }, "node_modules/validate-npm-package-name": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", - "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "dev": true, - "dependencies": { - "builtins": "^5.0.0" - }, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -19876,6 +20436,7 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.12.tgz", "integrity": "sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==", + "license": "MIT", "engines": { "node": ">=12" } @@ -19884,19 +20445,21 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/vite": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz", - "integrity": "sha512-sgnEEFTZYMui/sTlH1/XEnVNHMujOahPLGMxn1+5sIT45Xjng1Ec1K78jRP15dSmVgg5WBin9yO81j3o9OxofA==", + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", + "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.39", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -19944,10 +20507,11 @@ } }, "node_modules/vite-node": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz", - "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", + "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", "dev": true, + "license": "MIT", "dependencies": { "cac": "^6.7.14", "debug": "^4.3.4", @@ -19970,6 +20534,7 @@ "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz", "integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", @@ -19984,17 +20549,47 @@ } } }, + "node_modules/vite/node_modules/postcss": { + "version": "8.4.40", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", + "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", + "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": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/vitest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", - "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", + "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/expect": "1.4.0", - "@vitest/runner": "1.4.0", - "@vitest/snapshot": "1.4.0", - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", + "@vitest/expect": "1.6.0", + "@vitest/runner": "1.6.0", + "@vitest/snapshot": "1.6.0", + "@vitest/spy": "1.6.0", + "@vitest/utils": "1.6.0", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -20006,9 +20601,9 @@ "std-env": "^3.5.0", "strip-literal": "^2.0.0", "tinybench": "^2.5.1", - "tinypool": "^0.8.2", + "tinypool": "^0.8.3", "vite": "^5.0.0", - "vite-node": "1.4.0", + "vite-node": "1.6.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -20023,8 +20618,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.4.0", - "@vitest/ui": "1.4.0", + "@vitest/browser": "1.6.0", + "@vitest/ui": "1.6.0", "happy-dom": "*", "jsdom": "*" }, @@ -20054,6 +20649,7 @@ "resolved": "https://registry.npmjs.org/vitest-github-actions-reporter/-/vitest-github-actions-reporter-0.11.1.tgz", "integrity": "sha512-ZHHB0wBgOPhMYCB17WKVlJZa+5SdudBZFoVoebwfq3ioIUTeLQGYHwh85vpdJAxRghLP8d0qI/6eCTueGyDVXA==", "dev": true, + "license": "MIT", "dependencies": { "@actions/core": "^1.10.0" }, @@ -20069,6 +20665,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", @@ -20092,6 +20689,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, + "license": "MIT", "engines": { "node": ">=16" }, @@ -20104,6 +20702,7 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=16.17.0" } @@ -20113,6 +20712,7 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -20125,6 +20725,7 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -20137,6 +20738,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^4.0.0" }, @@ -20152,6 +20754,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, + "license": "MIT", "dependencies": { "mimic-fn": "^4.0.0" }, @@ -20167,6 +20770,7 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -20179,6 +20783,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { "node": ">=14" }, @@ -20191,6 +20796,7 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -20203,6 +20809,7 @@ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, + "license": "MIT", "dependencies": { "defaults": "^1.0.3" } @@ -20212,15 +20819,17 @@ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/webcrypto-core": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.7.8.tgz", - "integrity": "sha512-eBR98r9nQXTqXt/yDRtInszPMjTaSAMJAFDg2AHsgrnczawT1asx9YNBX6k5p+MekbPF4+s/UJJrr88zsTqkSg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.0.tgz", + "integrity": "sha512-kR1UQNH8MD42CYuLzvibfakG5Ew5seG85dMMoAM/1LqvckxaF6pUiidLuraIu4V+YCIFabYecUZAW0TuxAoaqw==", "dev": true, + "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.3.8", "@peculiar/json-schema": "^1.1.12", @@ -20232,12 +20841,14 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "license": "MIT", "engines": { "node": ">=12" } @@ -20246,6 +20857,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -20256,6 +20868,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -20271,6 +20884,7 @@ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "dev": true, + "license": "MIT", "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -20286,13 +20900,15 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/which-typed-array": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, + "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", @@ -20308,10 +20924,11 @@ } }, "node_modules/why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "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": { "siginfo": "^2.0.0", "stackback": "0.0.2" @@ -20327,6 +20944,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -20336,6 +20954,7 @@ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", "dev": true, + "license": "MIT", "dependencies": { "string-width": "^4.0.0" }, @@ -20343,17 +20962,29 @@ "node": ">=8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -20366,13 +20997,15 @@ "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==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -20394,6 +21027,7 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -20401,13 +21035,15 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/yaml": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", - "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", "dev": true, + "license": "ISC", "bin": { "yaml": "bin.mjs" }, @@ -20419,13 +21055,15 @@ "version": "0.0.43", "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -20444,6 +21082,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } @@ -20454,6 +21093,7 @@ "integrity": "sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==", "dev": true, "hasInstallScript": true, + "license": "BSD-2-Clause", "bin": { "yarn": "bin/yarn.js", "yarnpkg": "bin/yarn.js" @@ -20467,6 +21107,7 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -20476,12 +21117,22 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 81780d7c..bf359ba0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "fixit-api", - "version": "2.1.4", - "description": "Fixit API services built on NodeJS and Apollo GraphQL.", + "version": "2.2.0-next.1", + "description": "Fixit API services built on NodeJS, ExpressJS, and Apollo GraphQL.", "author": { "name": "Trevor Anderson", "email": "Trevor@Nerdware.cloud" @@ -17,7 +17,7 @@ "url": "https://github.com/Nerdware-LLC/fixit-api/issues" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" }, "type": "module", "scripts": { @@ -30,84 +30,86 @@ "test": "vitest run", "test:ci": "vitest run --coverage --bail=1 --silent", "test:types": "tsc --project tsconfig.json --noEmit --skipLibCheck", - "open-api:validate": "swagger-cli validate docs/open-api.yaml", - "open-api:update-types": "scripts/codegen.open-api.sh", - "open-api:update-remote": "scripts/codegen.open-api.sh --update-swaggerhub", - "gql:update-schema": "rover graph fetch fixit@current > fixit@current.graphql", - "gql:update-types": "graphql-codegen-esm --config codegen.ts" + "open-api:validate-schema": "redocly lint", + "open-api:update-types": "scripts/codegen.types-open-api.sh", + "graphql:update-types": "scripts/codegen.types-graphql.sh" }, "dependencies": { - "@apollo/server": "^4.10.1", - "@aws-sdk/client-dynamodb": "^3.391.0", - "@aws-sdk/client-lambda": "^3.391.0", - "@aws-sdk/lib-dynamodb": "^3.391.0", - "@graphql-tools/schema": "^10.0.0", - "@nerdware/ddb-single-table": "^2.4.0", - "@nerdware/ts-string-helpers": "^1.2.1", - "@nerdware/ts-type-safety-utils": "^1.0.8", - "@sentry/node": "^7.64.0", + "@apollo/server": "^4.10.5", + "@aws-sdk/client-dynamodb": "^3.623.0", + "@aws-sdk/client-lambda": "^3.623.0", + "@aws-sdk/client-pinpoint": "^3.623.0", + "@aws-sdk/lib-dynamodb": "^3.623.0", + "@graphql-tools/schema": "^10.0.4", + "@graphql-tools/utils": "^10.3.2", + "@nerdware/ddb-single-table": "^2.6.3", + "@nerdware/ts-string-helpers": "^1.7.0", + "@nerdware/ts-type-safety-utils": "^1.0.14", + "@sentry/node": "^7.118.0", "bcrypt": "^5.1.1", "chalk": "^5.3.0", "cors": "^2.8.5", - "dayjs": "^1.11.9", - "expo-server-sdk": "^3.7.0", + "dayjs": "^1.11.12", + "expo-server-sdk": "^3.10.0", "express": "^4.19.2", - "google-auth-library": "^9.7.0", - "graphql": "^16.8.1", - "graphql-tag": "^2.12.6", - "helmet": "^7.0.0", - "jsonwebtoken": "^9.0.1", + "google-auth-library": "^9.13.0", + "graphql": "^16.9.0", + "helmet": "^7.1.0", + "jsonwebtoken": "^9.0.2", "lodash.merge": "^4.6.2", - "stripe": "^10.7.0", - "uuid": "^9.0.0" + "stripe": "^10.17.0", + "uuid": "^10.0.0", + "zod": "^3.23.8" }, "devDependencies": { - "@apollo/rover": "^0.22.0", - "@graphql-codegen/cli": "^5.0.0", - "@graphql-codegen/introspection": "^4.0.0", - "@graphql-codegen/typescript": "^4.0.1", - "@graphql-codegen/typescript-resolvers": "^4.0.1", - "@graphql-tools/mock": "^9.0.0", + "@apollo/rover": "^0.23.0", + "@graphql-codegen/cli": "^5.0.2", + "@graphql-codegen/introspection": "^4.0.3", + "@graphql-codegen/typescript": "^4.0.9", + "@graphql-codegen/typescript-resolvers": "^4.2.1", + "@graphql-tools/mock": "^9.0.4", + "@redocly/cli": "^1.19.0", "@serverless-guru/prettier-plugin-import-order": "^0.4.2", - "@swc/cli": "^0.3.10", - "@swc/core": "^1.3.82", - "@types/bcrypt": "^5.0.0", - "@types/cors": "^2.8.13", - "@types/express": "^4.17.17", - "@types/jsonwebtoken": "^9.0.2", - "@types/lodash.merge": "^4.6.7", - "@types/node": "^20.5.0", + "@swc/cli": "^0.3.14", + "@swc/core": "^1.7.5", + "@types/bcrypt": "^5.0.2", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.6", + "@types/lodash.merge": "^4.6.9", + "@types/node": "^20.14.14", "@types/supertest": "^6.0.2", - "@types/uuid": "^9.0.2", - "@vitest/coverage-v8": "^1.3.1", - "aws-sdk-client-mock": "^3.0.0", - "eslint": "^8.47.0", + "@types/uuid": "^9.0.8", + "@vitest/coverage-v8": "^1.6.0", + "aws-sdk-client-mock": "^4.0.1", + "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", - "eslint-import-resolver-typescript": "^3.6.0", - "eslint-plugin-import": "^2.28.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-vitest": "^0.3.1", - "nodemon": "^3.0.1", - "openapi-typescript": "^7.0.0-next.7", - "prettier": "^3.2.5", - "supertest": "^6.3.3", - "swagger-cli": "^4.0.4", + "eslint-plugin-vitest": "^0.5.4", + "nodemon": "^3.1.4", + "openapi-typescript": "^7.3.0", + "prettier": "^3.3.3", + "stripe-event-types": "^3.1.0", + "supertest": "^7.0.0", "swaggerhub-cli": "^0.9.0", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "type-fest": "^4.2.0", - "typescript": "^5.1.6", - "typescript-eslint": "^7.1.1", - "vite": "^5.1.7", - "vite-tsconfig-paths": "^4.2.0", - "vitest": "^1.3.1", + "type-fest": "^4.23.0", + "typescript": "^5.5.4", + "typescript-eslint": "^7.18.0", + "vite": "^5.3.5", + "vite-tsconfig-paths": "^4.3.2", + "vitest": "^1.5.2", "vitest-github-actions-reporter": "^0.11.1" }, "overrides": { "@serverless-guru/prettier-plugin-import-order": { - "prettier": "^3.2.5" + "prettier": "$prettier" }, "swaggerhub-cli": { + "@oclif/plugin-plugins": ">=5", "ip": ">=2.0.1" } } diff --git a/redocly.yaml b/redocly.yaml new file mode 100644 index 00000000..0835bddf --- /dev/null +++ b/redocly.yaml @@ -0,0 +1,8 @@ +# See https://redocly.com/docs/cli/configuration/ + +extends: + - recommended + +apis: + fixit: + root: ./schemas/OpenAPI/open-api.yaml diff --git a/docs/endpoints/admin.csp-violation.yaml b/schemas/OpenAPI/endpoints/admin.csp-violation.yaml similarity index 82% rename from docs/endpoints/admin.csp-violation.yaml rename to schemas/OpenAPI/endpoints/admin.csp-violation.yaml index b92de174..6b47e8c9 100644 --- a/docs/endpoints/admin.csp-violation.yaml +++ b/schemas/OpenAPI/endpoints/admin.csp-violation.yaml @@ -4,7 +4,7 @@ post: operationId: CspViolation - summary: Logs CSP violation reports + summary: Logs CSP violation reports. security: [] tags: [admin] requestBody: @@ -12,6 +12,6 @@ post: application/csp-report: schema: { $ref: "../open-api.yaml#/components/schemas/CspViolationReport" } responses: - "200": { description: OK } + "204": { $ref: "../open-api.yaml#/components/responses/204NoContent" } "4XX": { $ref: "../open-api.yaml#/components/responses/UnexpectedResponse" } default: { $ref: "../open-api.yaml#/components/responses/UnexpectedResponse" } diff --git a/docs/endpoints/admin.healthcheck.yaml b/schemas/OpenAPI/endpoints/admin.healthcheck.yaml similarity index 72% rename from docs/endpoints/admin.healthcheck.yaml rename to schemas/OpenAPI/endpoints/admin.healthcheck.yaml index ebc89325..285968a0 100644 --- a/docs/endpoints/admin.healthcheck.yaml +++ b/schemas/OpenAPI/endpoints/admin.healthcheck.yaml @@ -1,15 +1,16 @@ ## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/path-item -# POST /admin/healthcheck +# GET /admin/healthcheck -post: +get: operationId: Healthcheck - summary: Responds to load balancer healthchecks + summary: Responds to load balancer healthchecks. security: [] tags: [admin] responses: "200": - description: OK + description: | + [200 OK](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200) content: application/json: schema: @@ -18,7 +19,7 @@ post: message: type: string enum: [SUCCESS] - description: The string constant "SUCCESS" + description: The string constant "SUCCESS". required: - message "4XX": { $ref: "../open-api.yaml#/components/responses/UnexpectedResponse" } diff --git a/docs/endpoints/auth.google-token.yaml b/schemas/OpenAPI/endpoints/auth.google-token.yaml similarity index 70% rename from docs/endpoints/auth.google-token.yaml rename to schemas/OpenAPI/endpoints/auth.google-token.yaml index 1e481cb4..afeeef7b 100644 --- a/docs/endpoints/auth.google-token.yaml +++ b/schemas/OpenAPI/endpoints/auth.google-token.yaml @@ -4,13 +4,13 @@ post: operationId: GoogleToken - summary: Processes JSON JWT payloads from GoogleID services (existing users only) + summary: Authenticates a user via Google OAuth JSON JWT from GoogleID services. security: [] tags: [auth] - requestBody: { $ref: "../open-api.yaml#/components/requestBodies/GoogleTokenRequest" } + requestBody: + $ref: "../open-api.yaml#/components/requestBodies/GoogleTokenRequest" responses: - # TODO Update 200 to use 200.AuthTokenAndPreFetchedUserItems, then run codegen - "200": { $ref: "../open-api.yaml#/components/responses/200AuthToken" } + "200": { $ref: "../open-api.yaml#/components/responses/200AuthTokenAndPreFetchedUserItems" } "400": { $ref: "../open-api.yaml#/components/responses/400InvalidUserInput" } "401": { $ref: "../open-api.yaml#/components/responses/401AuthenticationRequired" } "5XX": { $ref: "../open-api.yaml#/components/responses/5xxInternalServerError" } diff --git a/docs/endpoints/auth.login.yaml b/schemas/OpenAPI/endpoints/auth.login.yaml similarity index 79% rename from docs/endpoints/auth.login.yaml rename to schemas/OpenAPI/endpoints/auth.login.yaml index cdd2f7f1..eb7cddb5 100644 --- a/docs/endpoints/auth.login.yaml +++ b/schemas/OpenAPI/endpoints/auth.login.yaml @@ -4,10 +4,12 @@ post: operationId: Login - summary: Authenticates a user via login credentials + summary: | + Authenticates a user for the purposes of accessing protected resources. security: [] tags: [auth] - requestBody: { $ref: "../open-api.yaml#/components/requestBodies/LoginRequest" } + requestBody: + $ref: "../open-api.yaml#/components/requestBodies/LoginRequest" responses: "200": { $ref: "../open-api.yaml#/components/responses/200AuthTokenAndPreFetchedUserItems" } "400": { $ref: "../open-api.yaml#/components/responses/400InvalidUserInput" } diff --git a/schemas/OpenAPI/endpoints/auth.password-reset-init.yaml b/schemas/OpenAPI/endpoints/auth.password-reset-init.yaml new file mode 100644 index 00000000..c84f63ed --- /dev/null +++ b/schemas/OpenAPI/endpoints/auth.password-reset-init.yaml @@ -0,0 +1,16 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/path-item + +# POST /auth/password-reset-init + +post: + operationId: PasswordResetInit + summary: Initiates the password-reset flow for a user. + security: [] + tags: [auth] + requestBody: + $ref: "../open-api.yaml#/components/requestBodies/PasswordResetInitRequest" + responses: + "200": { $ref: "../open-api.yaml#/components/responses/200OK" } + "400": { $ref: "../open-api.yaml#/components/responses/400InvalidUserInput" } + "5XX": { $ref: "../open-api.yaml#/components/responses/5xxInternalServerError" } + default: { $ref: "../open-api.yaml#/components/responses/UnexpectedResponse" } diff --git a/schemas/OpenAPI/endpoints/auth.password-reset.yaml b/schemas/OpenAPI/endpoints/auth.password-reset.yaml new file mode 100644 index 00000000..25b85f41 --- /dev/null +++ b/schemas/OpenAPI/endpoints/auth.password-reset.yaml @@ -0,0 +1,16 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/path-item + +# POST /auth/password-reset + +post: + operationId: PasswordReset + summary: Updates the user's password to complete the password-reset flow. + security: [] + tags: [auth] + requestBody: + $ref: "../open-api.yaml#/components/requestBodies/PasswordResetRequest" + responses: + "200": { $ref: "../open-api.yaml#/components/responses/200OK" } + "400": { $ref: "../open-api.yaml#/components/responses/400InvalidUserInput" } + "5XX": { $ref: "../open-api.yaml#/components/responses/5xxInternalServerError" } + default: { $ref: "../open-api.yaml#/components/responses/UnexpectedResponse" } diff --git a/docs/endpoints/auth.register.yaml b/schemas/OpenAPI/endpoints/auth.register.yaml similarity index 72% rename from docs/endpoints/auth.register.yaml rename to schemas/OpenAPI/endpoints/auth.register.yaml index 53a40acc..0a2a533e 100644 --- a/docs/endpoints/auth.register.yaml +++ b/schemas/OpenAPI/endpoints/auth.register.yaml @@ -4,12 +4,13 @@ post: operationId: Register - summary: Registers a new user + summary: Registers a new user. security: [] tags: [auth] - requestBody: { $ref: "../open-api.yaml#/components/requestBodies/UserRegistrationRequest" } + requestBody: + $ref: "../open-api.yaml#/components/requestBodies/UserRegistrationRequest" responses: - "200": { $ref: "../open-api.yaml#/components/responses/200AuthToken" } + "201": { $ref: "../open-api.yaml#/components/responses/201AuthToken" } "400": { $ref: "../open-api.yaml#/components/responses/400InvalidUserInput" } "5XX": { $ref: "../open-api.yaml#/components/responses/5xxInternalServerError" } default: { $ref: "../open-api.yaml#/components/responses/UnexpectedResponse" } diff --git a/docs/endpoints/auth.token.yaml b/schemas/OpenAPI/endpoints/auth.token.yaml similarity index 80% rename from docs/endpoints/auth.token.yaml rename to schemas/OpenAPI/endpoints/auth.token.yaml index 7b750c94..1f649894 100644 --- a/docs/endpoints/auth.token.yaml +++ b/schemas/OpenAPI/endpoints/auth.token.yaml @@ -4,10 +4,11 @@ post: operationId: RefreshToken - summary: Refreshes a user's auth token + summary: Refreshes an existing user's auth token. security: [{ JwtBearerAuth: [] }] tags: [auth] - requestBody: { $ref: "../open-api.yaml#/components/requestBodies/RefreshAuthTokenRequest" } + requestBody: + $ref: "../open-api.yaml#/components/requestBodies/RefreshAuthTokenRequest" responses: "200": { $ref: "../open-api.yaml#/components/responses/200AuthTokenAndPreFetchedUserItems" } "401": { $ref: "../open-api.yaml#/components/responses/401AuthenticationRequired" } diff --git a/docs/endpoints/connect.account-link.yaml b/schemas/OpenAPI/endpoints/connect.account-link.yaml similarity index 81% rename from docs/endpoints/connect.account-link.yaml rename to schemas/OpenAPI/endpoints/connect.account-link.yaml index 4d140894..b89d339f 100644 --- a/docs/endpoints/connect.account-link.yaml +++ b/schemas/OpenAPI/endpoints/connect.account-link.yaml @@ -4,12 +4,13 @@ post: operationId: ConnectAccountLink - summary: Provides a link to the Stripe Connect Account onboarding portal + summary: Provides a link to the Stripe Connect Account onboarding portal. security: [{ JwtBearerAuth: [] }] tags: [stripe, stripe-connect, stripe-link] - requestBody: { $ref: "../open-api.yaml#/components/requestBodies/StripeLinkRequest" } + requestBody: + $ref: "../open-api.yaml#/components/requestBodies/StripeLinkRequest" responses: - "200": { $ref: "../open-api.yaml#/components/responses/200StripeLink" } + "201": { $ref: "../open-api.yaml#/components/responses/201StripeLink" } "400": { $ref: "../open-api.yaml#/components/responses/400InvalidUserInput" } "401": { $ref: "../open-api.yaml#/components/responses/401AuthenticationRequired" } "5XX": { $ref: "../open-api.yaml#/components/responses/5xxInternalServerError" } diff --git a/docs/endpoints/connect.dashboard-link.yaml b/schemas/OpenAPI/endpoints/connect.dashboard-link.yaml similarity index 89% rename from docs/endpoints/connect.dashboard-link.yaml rename to schemas/OpenAPI/endpoints/connect.dashboard-link.yaml index 61615376..761f5714 100644 --- a/docs/endpoints/connect.dashboard-link.yaml +++ b/schemas/OpenAPI/endpoints/connect.dashboard-link.yaml @@ -4,11 +4,11 @@ get: operationId: ConnectDashboardLink - summary: Provides a link to the Stripe Connect Account dashboard portal + summary: Provides a link to the Stripe Connect Account dashboard portal. security: [{ JwtBearerAuth: [] }] tags: [stripe, stripe-connect, stripe-link] responses: - "200": { $ref: "../open-api.yaml#/components/responses/200StripeLink" } + "201": { $ref: "../open-api.yaml#/components/responses/201StripeLink" } "401": { $ref: "../open-api.yaml#/components/responses/401AuthenticationRequired" } "5XX": { $ref: "../open-api.yaml#/components/responses/5xxInternalServerError" } default: { $ref: "../open-api.yaml#/components/responses/UnexpectedResponse" } diff --git a/docs/endpoints/subscriptions.check-promo-code.yaml b/schemas/OpenAPI/endpoints/subscriptions.check-promo-code.yaml similarity index 82% rename from docs/endpoints/subscriptions.check-promo-code.yaml rename to schemas/OpenAPI/endpoints/subscriptions.check-promo-code.yaml index e59659c2..1b201668 100644 --- a/docs/endpoints/subscriptions.check-promo-code.yaml +++ b/schemas/OpenAPI/endpoints/subscriptions.check-promo-code.yaml @@ -4,10 +4,11 @@ post: operationId: CheckPromoCode - summary: Checks promo code validity + summary: Checks the validity of the provided promo code. security: [{ JwtBearerAuth: [] }] tags: [checkout, subscriptions] - requestBody: { $ref: "../open-api.yaml#/components/requestBodies/CheckPromoCodeRequest" } + requestBody: + $ref: "../open-api.yaml#/components/requestBodies/CheckPromoCodeRequest" responses: "200": { $ref: "../open-api.yaml#/components/responses/200CheckPromoCode" } "400": { $ref: "../open-api.yaml#/components/responses/400InvalidUserInput" } diff --git a/docs/endpoints/subscriptions.customer-portal.yaml b/schemas/OpenAPI/endpoints/subscriptions.customer-portal.yaml similarity index 75% rename from docs/endpoints/subscriptions.customer-portal.yaml rename to schemas/OpenAPI/endpoints/subscriptions.customer-portal.yaml index ffebb00a..928e4e1e 100644 --- a/docs/endpoints/subscriptions.customer-portal.yaml +++ b/schemas/OpenAPI/endpoints/subscriptions.customer-portal.yaml @@ -4,12 +4,13 @@ post: operationId: SubscriptionsCustomerPortal - summary: Provides a link to the Stripe Customer portal + summary: Provides a link to the Stripe Customer portal. security: [{ JwtBearerAuth: [] }] tags: [stripe, stripe-link] - requestBody: { $ref: "../open-api.yaml#/components/requestBodies/StripeLinkRequest" } + requestBody: + $ref: "../open-api.yaml#/components/requestBodies/StripeLinkRequest" responses: - "200": { $ref: "../open-api.yaml#/components/responses/200StripeLink" } + "201": { $ref: "../open-api.yaml#/components/responses/201StripeLink" } "400": { $ref: "../open-api.yaml#/components/responses/400InvalidUserInput" } "401": { $ref: "../open-api.yaml#/components/responses/401AuthenticationRequired" } "5XX": { $ref: "../open-api.yaml#/components/responses/5xxInternalServerError" } diff --git a/docs/endpoints/subscriptions.submit-payment.yaml b/schemas/OpenAPI/endpoints/subscriptions.submit-payment.yaml similarity index 91% rename from docs/endpoints/subscriptions.submit-payment.yaml rename to schemas/OpenAPI/endpoints/subscriptions.submit-payment.yaml index f235569d..6562e857 100644 --- a/docs/endpoints/subscriptions.submit-payment.yaml +++ b/schemas/OpenAPI/endpoints/subscriptions.submit-payment.yaml @@ -4,7 +4,7 @@ post: operationId: SubscriptionsSubmitPayment - summary: Processes checkout payment information + summary: Processes checkout payment information. security: [{ JwtBearerAuth: [] }] tags: [checkout, subscriptions, stripe] requestBody: @@ -22,7 +22,7 @@ post: - selectedSubscription - paymentMethodID responses: - "200": { $ref: "../open-api.yaml#/components/responses/200AuthTokenAndCheckoutCompletionInfo" } + "201": { $ref: "../open-api.yaml#/components/responses/201AuthTokenAndCheckoutCompletionInfo" } "400": { $ref: "../open-api.yaml#/components/responses/400InvalidUserInput" } "401": { $ref: "../open-api.yaml#/components/responses/401AuthenticationRequired" } "5XX": { $ref: "../open-api.yaml#/components/responses/5xxInternalServerError" } diff --git a/schemas/OpenAPI/enums/InvoiceStatus.yaml b/schemas/OpenAPI/enums/InvoiceStatus.yaml new file mode 100644 index 00000000..3a126c79 --- /dev/null +++ b/schemas/OpenAPI/enums/InvoiceStatus.yaml @@ -0,0 +1,6 @@ +type: string +description: The Invoice's status. +enum: + - OPEN + - CLOSED + - DISPUTED diff --git a/schemas/OpenAPI/enums/SubscriptionPriceName.yaml b/schemas/OpenAPI/enums/SubscriptionPriceName.yaml new file mode 100644 index 00000000..6742ae6e --- /dev/null +++ b/schemas/OpenAPI/enums/SubscriptionPriceName.yaml @@ -0,0 +1,15 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/schema + +# REST Schema: SubscriptionPriceName + +type: string +enum: + - ANNUAL + - MONTHLY + - TRIAL +externalDocs: + description: GQL Apollo Studio Schema Reference for the SubscriptionPriceName enum. + url: "https://studio.apollographql.com/public/fixit/variant/current/schema/reference/enums/SubscriptionPriceName" +description: | + The Fixit Subscription price name — this value corresponds to the [Stripe Price + "nickname" field](https://stripe.com/docs/api/prices/object#price_object-nickname). diff --git a/schemas/OpenAPI/enums/SubscriptionStatus.yaml b/schemas/OpenAPI/enums/SubscriptionStatus.yaml new file mode 100644 index 00000000..f8b640b8 --- /dev/null +++ b/schemas/OpenAPI/enums/SubscriptionStatus.yaml @@ -0,0 +1,12 @@ +type: string +description: | + The Subscription's status, as provided by Stripe. + See https://docs.stripe.com/api/subscriptions/object#subscription_object-status +enum: + - active + - incomplete + - incomplete_expired + - trialing + - past_due + - canceled + - unpaid diff --git a/schemas/OpenAPI/enums/WorkOrderCategory.yaml b/schemas/OpenAPI/enums/WorkOrderCategory.yaml new file mode 100644 index 00000000..c31d1b7a --- /dev/null +++ b/schemas/OpenAPI/enums/WorkOrderCategory.yaml @@ -0,0 +1,21 @@ +type: + - string + - "null" +description: The WorkOrder's category. +enum: + - null + - DRYWALL + - ELECTRICAL + - FLOORING + - GENERAL + - HVAC + - LANDSCAPING + - MASONRY + - PAINTING + - PAVING + - PEST + - PLUMBING + - ROOFING + - TRASH + - TURNOVER + - WINDOWS diff --git a/schemas/OpenAPI/enums/WorkOrderPriority.yaml b/schemas/OpenAPI/enums/WorkOrderPriority.yaml new file mode 100644 index 00000000..83482949 --- /dev/null +++ b/schemas/OpenAPI/enums/WorkOrderPriority.yaml @@ -0,0 +1,6 @@ +type: string +description: The WorkOrder's priority. +enum: + - LOW + - NORMAL + - HIGH diff --git a/schemas/OpenAPI/enums/WorkOrderStatus.yaml b/schemas/OpenAPI/enums/WorkOrderStatus.yaml new file mode 100644 index 00000000..57ab82b0 --- /dev/null +++ b/schemas/OpenAPI/enums/WorkOrderStatus.yaml @@ -0,0 +1,9 @@ +type: string +description: The WorkOrder's status. +enum: + - UNASSIGNED + - ASSIGNED + - IN_PROGRESS + - DEFERRED + - CANCELLED + - COMPLETE diff --git a/schemas/OpenAPI/objectProperties/createdAt.yaml b/schemas/OpenAPI/objectProperties/createdAt.yaml new file mode 100644 index 00000000..d183fa07 --- /dev/null +++ b/schemas/OpenAPI/objectProperties/createdAt.yaml @@ -0,0 +1,3 @@ +type: string +format: date-time +description: Timestamp which indicates when the resource was created. diff --git a/schemas/OpenAPI/objectProperties/email.yaml b/schemas/OpenAPI/objectProperties/email.yaml new file mode 100644 index 00000000..bd3851e5 --- /dev/null +++ b/schemas/OpenAPI/objectProperties/email.yaml @@ -0,0 +1,3 @@ +type: string +format: email +description: User's email address. diff --git a/schemas/OpenAPI/objectProperties/googleIDToken.yaml b/schemas/OpenAPI/objectProperties/googleIDToken.yaml new file mode 100644 index 00000000..aa72329b --- /dev/null +++ b/schemas/OpenAPI/objectProperties/googleIDToken.yaml @@ -0,0 +1,3 @@ +type: string +description: | + Base64URL-encoded JSON JWT from GoogleID services (auth: google-oauth). diff --git a/docs/objectProperties/handle.yaml b/schemas/OpenAPI/objectProperties/handle.yaml similarity index 66% rename from docs/objectProperties/handle.yaml rename to schemas/OpenAPI/objectProperties/handle.yaml index 188ad1c7..0e8c8103 100644 --- a/docs/objectProperties/handle.yaml +++ b/schemas/OpenAPI/objectProperties/handle.yaml @@ -1,5 +1,5 @@ type: string -description: A user's Fixit handle. +description: User's Fixit handle. pattern: "^@[a-zA-Z0-9_]{3,50}$" examples: - "@foo_user" diff --git a/docs/objectProperties/password.yaml b/schemas/OpenAPI/objectProperties/password.yaml similarity index 85% rename from docs/objectProperties/password.yaml rename to schemas/OpenAPI/objectProperties/password.yaml index ecc0cfe7..1bb1e39a 100644 --- a/docs/objectProperties/password.yaml +++ b/schemas/OpenAPI/objectProperties/password.yaml @@ -4,7 +4,7 @@ minLength: 6 maxLength: 250 pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])[A-Za-z0-9!@#$%^&*]{6,250}$" description: | - The user's password (auth: local). In order to be valid, a password must meet + User's password (auth: local). In order to be valid, a password must meet all of the following criteria: - Contains at least one lowercase letter. - Contains at least one uppercase letter. diff --git a/schemas/OpenAPI/objectProperties/passwordResetToken.yaml b/schemas/OpenAPI/objectProperties/passwordResetToken.yaml new file mode 100644 index 00000000..a439683a --- /dev/null +++ b/schemas/OpenAPI/objectProperties/passwordResetToken.yaml @@ -0,0 +1,3 @@ +type: string +description: | + A valid password-reset token for securely resetting a user's password. diff --git a/docs/objectProperties/paymentMethodID.yaml b/schemas/OpenAPI/objectProperties/paymentMethodID.yaml similarity index 100% rename from docs/objectProperties/paymentMethodID.yaml rename to schemas/OpenAPI/objectProperties/paymentMethodID.yaml diff --git a/docs/objectProperties/phone.yaml b/schemas/OpenAPI/objectProperties/phone.yaml similarity index 74% rename from docs/objectProperties/phone.yaml rename to schemas/OpenAPI/objectProperties/phone.yaml index ea5167bd..4d6e91ec 100644 --- a/docs/objectProperties/phone.yaml +++ b/schemas/OpenAPI/objectProperties/phone.yaml @@ -2,7 +2,7 @@ type: - string - "null" description: | - A user's phone number. Currently this API only supports US phone numbers. All + User's phone number. Currently this API only supports US phone numbers. All whitespace, non-numeric characters, and country/calling code prefixes will be stripped from the phone number upon receipt, so "+1 (555) 555-5555" will be treated the same as "5555555555". diff --git a/docs/objectProperties/promoCode.yaml b/schemas/OpenAPI/objectProperties/promoCode.yaml similarity index 100% rename from docs/objectProperties/promoCode.yaml rename to schemas/OpenAPI/objectProperties/promoCode.yaml diff --git a/docs/objectProperties/returnURL.yaml b/schemas/OpenAPI/objectProperties/returnURL.yaml similarity index 100% rename from docs/objectProperties/returnURL.yaml rename to schemas/OpenAPI/objectProperties/returnURL.yaml diff --git a/schemas/OpenAPI/objectProperties/stripeCustomerID.yaml b/schemas/OpenAPI/objectProperties/stripeCustomerID.yaml new file mode 100644 index 00000000..2ddbb6f5 --- /dev/null +++ b/schemas/OpenAPI/objectProperties/stripeCustomerID.yaml @@ -0,0 +1,2 @@ +type: string +description: User's Stripe Customer ID. diff --git a/docs/objectProperties/updatedAt.yaml b/schemas/OpenAPI/objectProperties/updatedAt.yaml similarity index 100% rename from docs/objectProperties/updatedAt.yaml rename to schemas/OpenAPI/objectProperties/updatedAt.yaml diff --git a/schemas/OpenAPI/objects/AuthTokenPayload.yaml b/schemas/OpenAPI/objects/AuthTokenPayload.yaml new file mode 100644 index 00000000..f5644985 --- /dev/null +++ b/schemas/OpenAPI/objects/AuthTokenPayload.yaml @@ -0,0 +1,32 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/schema + +# REST Schema: AuthTokenPayload + +type: object +description: User's Fixit API auth token payload object. +# prettier-ignore +properties: + id: { type: string, description: An identifier for the user. } + handle: { $ref: "../open-api.yaml#/components/schemas/Handle" } + email: { $ref: "../open-api.yaml#/components/schemas/Email" } + phone: { $ref: "../open-api.yaml#/components/schemas/Phone" } + profile: { $ref: "../open-api.yaml#/components/schemas/UserProfile" } + stripeCustomerID: { $ref: "../open-api.yaml#/components/schemas/StripeCustomerID" } + stripeConnectAccount: { $ref: "../open-api.yaml#/components/schemas/AuthTokenPayloadStripeConnectAccountInfo" } + subscription: + oneOf: + - $ref: "../open-api.yaml#/components/schemas/AuthTokenPayloadSubscriptionInfo" + - { type: "null", description: "The user does not have a subscription." } + createdAt: { $ref: "../open-api.yaml#/components/schemas/CreatedAt" } + updatedAt: { $ref: "../open-api.yaml#/components/schemas/UpdatedAt" } +required: + - id + - handle + - email + - phone + - profile + - stripeCustomerID + - stripeConnectAccount + - subscription + - createdAt + - updatedAt diff --git a/schemas/OpenAPI/objects/AuthTokenPayloadStripeConnectAccountInfo.yaml b/schemas/OpenAPI/objects/AuthTokenPayloadStripeConnectAccountInfo.yaml new file mode 100644 index 00000000..8f42710a --- /dev/null +++ b/schemas/OpenAPI/objects/AuthTokenPayloadStripeConnectAccountInfo.yaml @@ -0,0 +1,35 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/schema + +# REST Schema: AuthTokenPayloadStripeConnectAccountInfo + +type: object +description: | + An object within the payload of a user's Fixit API auth token with data + relating to their current status in the Stripe Connect onboarding flow. +externalDocs: + description: GQL Apollo Studio Schema Reference for the UserStripeConnectAccount object. + url: https://studio.apollographql.com/public/fixit/variant/current/schema/reference/objects/UserStripeConnectAccount +properties: + id: + type: string + description: An identifier for the Stripe Connect Account. + detailsSubmitted: + type: boolean + description: | + A boolean indicating whether the user has submitted their details to + Stripe Connect in the onboarding flow. + chargesEnabled: + type: boolean + description: | + A boolean indicating whether the user has enabled charges on their + Stripe Connect Account. + payoutsEnabled: + type: boolean + description: | + A boolean indicating whether the user has enabled payouts on their + Stripe Connect Account. +required: + - id + - detailsSubmitted + - chargesEnabled + - payoutsEnabled diff --git a/schemas/OpenAPI/objects/AuthTokenPayloadSubscriptionInfo.yaml b/schemas/OpenAPI/objects/AuthTokenPayloadSubscriptionInfo.yaml new file mode 100644 index 00000000..0cb7c8f3 --- /dev/null +++ b/schemas/OpenAPI/objects/AuthTokenPayloadSubscriptionInfo.yaml @@ -0,0 +1,22 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/schema + +# REST Schema: AuthTokenPayloadSubscriptionInfo + +type: object +description: | + An object within the payload of a user's Fixit API auth token with data + relating to their current Fixit Subscription status. +externalDocs: + description: GQL Apollo Studio Schema Reference for the UserSubscription object. + url: https://studio.apollographql.com/public/fixit/variant/current/schema/reference/objects/UserSubscription +properties: + id: { type: string, description: An identifier for the subscription. } + status: { $ref: "../open-api.yaml#/components/schemas/SubscriptionStatus" } + currentPeriodEnd: + type: string + format: date-time + description: Timestamp indicating the end of the current billing period. +required: + - id + - status + - currentPeriodEnd diff --git a/docs/schemas/AuthTokenResponseField.yaml b/schemas/OpenAPI/objects/AuthTokenResponseField.yaml similarity index 100% rename from docs/schemas/AuthTokenResponseField.yaml rename to schemas/OpenAPI/objects/AuthTokenResponseField.yaml diff --git a/docs/schemas/CheckoutCompletionInfo.yaml b/schemas/OpenAPI/objects/CheckoutCompletionInfo.yaml similarity index 100% rename from docs/schemas/CheckoutCompletionInfo.yaml rename to schemas/OpenAPI/objects/CheckoutCompletionInfo.yaml diff --git a/docs/schemas/CheckoutCompletionInfoResponseField.yaml b/schemas/OpenAPI/objects/CheckoutCompletionInfoResponseField.yaml similarity index 100% rename from docs/schemas/CheckoutCompletionInfoResponseField.yaml rename to schemas/OpenAPI/objects/CheckoutCompletionInfoResponseField.yaml diff --git a/docs/schemas/Contact.yaml b/schemas/OpenAPI/objects/Contact.yaml similarity index 70% rename from docs/schemas/Contact.yaml rename to schemas/OpenAPI/objects/Contact.yaml index 98bbfbbf..58c9c3dc 100644 --- a/docs/schemas/Contact.yaml +++ b/schemas/OpenAPI/objects/Contact.yaml @@ -7,9 +7,15 @@ description: A pre-fetched Contact object returned from a REST endpoint. externalDocs: description: GQL Apollo Studio Schema Reference for the Contact object. url: "https://studio.apollographql.com/public/fixit/variant/current/schema/reference/objects/Contact" -required: [id, handle, email, phone, profile, createdAt, updatedAt] +required: [__typename, id, handle, email, phone, profile, createdAt, updatedAt] properties: - id: { type: string, description: The contact's ID } + __typename: + type: string + enum: [Contact] + description: | + The object's GraphQL type name, `"Contact"`, included to facilitate + writing pre-fetched objects into the front-end's Apollo Client cache. + id: { type: string, description: The contact's user ID } handle: { $ref: "../open-api.yaml#/components/schemas/Handle" } email: { $ref: "../open-api.yaml#/components/schemas/Email" } phone: { $ref: "../open-api.yaml#/components/schemas/Phone" } diff --git a/schemas/OpenAPI/objects/CspViolationReport.yaml b/schemas/OpenAPI/objects/CspViolationReport.yaml new file mode 100644 index 00000000..7fb28627 --- /dev/null +++ b/schemas/OpenAPI/objects/CspViolationReport.yaml @@ -0,0 +1,61 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/schema + +# REST Schema: CSP Violation Report + +type: object +description: A Content Security Policy (CSP) violation report. +externalDocs: + description: CSP directives documentation + url: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy" +properties: + document-uri: + type: string + description: | + The URI of the protected resource that was violated. + blocked-uri: + type: string + description: | + The URI of the resource that was blocked from loading. + status-code: + type: integer + description: | + The HTTP status code of the resource that was blocked from loading. + referrer: + type: string + description: | + The referrer of the protected resource that was violated. + script-sample: + type: string + description: | + The first 40 characters of the inline script, event handler, or style + that caused the violation. + original-policy: + type: string + description: | + The original policy as specified by the Content-Security-Policy header. + disposition: + type: string + enum: [enforce, report] + description: | + Either "enforce" or "report" depending on whether the Content-Security-Policy + header or the Content-Security-Policy-Report-Only header is used. + violated-directive: + type: string + description: | + The directive whose enforcement was violated (e.g. "default-src 'self'"). + effective-directive: + type: string + description: | + The effective directive that was violated (e.g. 'img-src'). + source-file: + type: string + description: | + The URI of the resource where the violation occurred. + line-number: + type: integer + description: | + The line number in the resource where the violation occurred. + column-number: + type: integer + description: | + The column number in the resource where the violation occurred. diff --git a/docs/schemas/Error.yaml b/schemas/OpenAPI/objects/Error.yaml similarity index 100% rename from docs/schemas/Error.yaml rename to schemas/OpenAPI/objects/Error.yaml diff --git a/docs/schemas/ExpoPushTokenParam.yaml b/schemas/OpenAPI/objects/ExpoPushTokenParam.yaml similarity index 100% rename from docs/schemas/ExpoPushTokenParam.yaml rename to schemas/OpenAPI/objects/ExpoPushTokenParam.yaml diff --git a/docs/schemas/GoogleIDTokenField.yaml b/schemas/OpenAPI/objects/GoogleIDTokenField.yaml similarity index 100% rename from docs/schemas/GoogleIDTokenField.yaml rename to schemas/OpenAPI/objects/GoogleIDTokenField.yaml diff --git a/docs/schemas/Invoice.yaml b/schemas/OpenAPI/objects/Invoice.yaml similarity index 67% rename from docs/schemas/Invoice.yaml rename to schemas/OpenAPI/objects/Invoice.yaml index 180df0b1..835041ab 100644 --- a/docs/schemas/Invoice.yaml +++ b/schemas/OpenAPI/objects/Invoice.yaml @@ -5,38 +5,41 @@ type: object description: A pre-fetched Invoice object returned from a Fixit REST endpoint. externalDocs: - description: GQL Apollo Studio Schema Reference for the WorkOrder object. + description: GQL Apollo Studio Schema Reference for the Invoice object. url: "https://studio.apollographql.com/public/fixit/variant/current/schema/reference/objects/Invoice" -required: [id, createdBy, assignedTo, amount, status, createdAt, updatedAt] +required: [__typename, id, createdBy, assignedTo, amount, status, createdAt, updatedAt] properties: + __typename: + type: string + enum: [Invoice] + description: | + The object's GraphQL type name, `"Invoice"`, included to facilitate + writing pre-fetched objects into the front-end's Apollo Client cache. id: type: string description: The invoice's ID. createdBy: type: object - description: The user or contact who created the invoice. + description: The user who created the invoice. required: [id] properties: id: type: string - description: The ID of the user or contact who created the invoice. + description: The ID of the user who created the invoice. assignedTo: type: object - description: The user or contact to whom the invoice is assigned. + description: The user to whom the invoice is assigned. required: [id] properties: id: type: string - description: The ID of the user or contact to whom the invoice is assigned. + description: The ID of the user to whom the invoice is assigned. amount: type: integer description: | The Invoice amount, represented as an integer which reflects USD centage (i.e., an 'amount' of 1 = $0.01 USD). - status: - type: string - description: The invoice's status. - enum: [OPEN, CLOSED, DISPUTED] + status: { $ref: "../open-api.yaml#/components/schemas/InvoiceStatus" } stripePaymentIntentID: type: - string diff --git a/docs/schemas/Location.yaml b/schemas/OpenAPI/objects/Location.yaml similarity index 100% rename from docs/schemas/Location.yaml rename to schemas/OpenAPI/objects/Location.yaml diff --git a/docs/schemas/LoginCredentials.GoogleOAuth.yaml b/schemas/OpenAPI/objects/LoginCredentials.GoogleOAuth.yaml similarity index 100% rename from docs/schemas/LoginCredentials.GoogleOAuth.yaml rename to schemas/OpenAPI/objects/LoginCredentials.GoogleOAuth.yaml diff --git a/docs/schemas/LoginCredentials.Local.yaml b/schemas/OpenAPI/objects/LoginCredentials.Local.yaml similarity index 100% rename from docs/schemas/LoginCredentials.Local.yaml rename to schemas/OpenAPI/objects/LoginCredentials.Local.yaml diff --git a/docs/schemas/LoginCredentials.yaml b/schemas/OpenAPI/objects/LoginCredentials.yaml similarity index 100% rename from docs/schemas/LoginCredentials.yaml rename to schemas/OpenAPI/objects/LoginCredentials.yaml diff --git a/docs/schemas/LoginParams.yaml b/schemas/OpenAPI/objects/LoginParams.yaml similarity index 100% rename from docs/schemas/LoginParams.yaml rename to schemas/OpenAPI/objects/LoginParams.yaml diff --git a/schemas/OpenAPI/objects/PasswordResetInitParams.yaml b/schemas/OpenAPI/objects/PasswordResetInitParams.yaml new file mode 100644 index 00000000..742b3ce2 --- /dev/null +++ b/schemas/OpenAPI/objects/PasswordResetInitParams.yaml @@ -0,0 +1,10 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/schema + +# REST Schema: PasswordResetInitParams + +type: object +description: The email address of the user initiating a password reset +properties: + email: { $ref: "../open-api.yaml#/components/schemas/Email" } +required: + - email diff --git a/schemas/OpenAPI/objects/PasswordResetParams.yaml b/schemas/OpenAPI/objects/PasswordResetParams.yaml new file mode 100644 index 00000000..98030a30 --- /dev/null +++ b/schemas/OpenAPI/objects/PasswordResetParams.yaml @@ -0,0 +1,14 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/schema + +# REST Schema: PasswordResetParams + +type: object +description: A new password and a valid password reset token +properties: + password: + $ref: "../open-api.yaml#/components/schemas/Password" + passwordResetToken: + $ref: "../open-api.yaml#/components/schemas/PasswordResetToken" +required: + - password + - passwordResetToken diff --git a/schemas/OpenAPI/objects/PreFetchedUserItems.yaml b/schemas/OpenAPI/objects/PreFetchedUserItems.yaml new file mode 100644 index 00000000..64113fbc --- /dev/null +++ b/schemas/OpenAPI/objects/PreFetchedUserItems.yaml @@ -0,0 +1,51 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/schema + +# REST Schema: PreFetchedUserItems + +type: object +description: | + A User's pre-fetched WorkOrders, Invoices, and Contacts, which are written + into the client's Apollo Client cache on the front-end (used on logins). + This object's properties correspond to GraphQL queries of the same name. +required: [myWorkOrders, myInvoices, myContacts] +properties: + myWorkOrders: + type: object + description: Pre-fetched `myWorkOrders` query objects for the front-end cache. + externalDocs: + description: Apollo Studio Schema Reference for the `myWorkOrders` GQL query. + url: "https://studio.apollographql.com/public/fixit/variant/current/schema/reference#myWorkOrders" + required: [createdByUser, assignedToUser] + properties: + createdByUser: + type: array + description: Work orders created by the user. + items: { $ref: "../open-api.yaml#/components/schemas/WorkOrder" } + assignedToUser: + type: array + description: Work orders assigned to the user. + items: { $ref: "../open-api.yaml#/components/schemas/WorkOrder" } + myInvoices: + type: object + description: Pre-fetched `myInvoices` query objects for the front-end cache. + externalDocs: + description: Apollo Studio Schema Reference for the `myInvoices` GQL query. + url: "https://studio.apollographql.com/public/fixit/variant/current/schema/reference#myInvoices" + required: [createdByUser, assignedToUser] + properties: + createdByUser: + type: array + description: Invoices created by the user. + items: { $ref: "../open-api.yaml#/components/schemas/Invoice" } + assignedToUser: + type: array + description: Invoices assigned to the user. + items: { $ref: "../open-api.yaml#/components/schemas/Invoice" } + myContacts: + type: array + description: Pre-fetched `myContacts` query objects for the front-end cache. + externalDocs: + description: Apollo Studio Schema Reference for the `myInvoices` GQL query. + url: "https://studio.apollographql.com/public/fixit/variant/current/schema/reference#myInvoices" + items: + $ref: "../open-api.yaml#/components/schemas/Contact" diff --git a/docs/schemas/PreFetchedUserItemsResponseField.yaml b/schemas/OpenAPI/objects/PreFetchedUserItemsResponseField.yaml similarity index 94% rename from docs/schemas/PreFetchedUserItemsResponseField.yaml rename to schemas/OpenAPI/objects/PreFetchedUserItemsResponseField.yaml index 2f6d7249..6688e1bf 100644 --- a/docs/schemas/PreFetchedUserItemsResponseField.yaml +++ b/schemas/OpenAPI/objects/PreFetchedUserItemsResponseField.yaml @@ -8,3 +8,5 @@ description: | properties: userItems: $ref: "../open-api.yaml#/components/schemas/PreFetchedUserItems" +required: + - userItems diff --git a/docs/schemas/PromoCodeInfo.yaml b/schemas/OpenAPI/objects/PromoCodeInfo.yaml similarity index 100% rename from docs/schemas/PromoCodeInfo.yaml rename to schemas/OpenAPI/objects/PromoCodeInfo.yaml diff --git a/docs/schemas/PromoCodeInfoResponseField.yaml b/schemas/OpenAPI/objects/PromoCodeInfoResponseField.yaml similarity index 100% rename from docs/schemas/PromoCodeInfoResponseField.yaml rename to schemas/OpenAPI/objects/PromoCodeInfoResponseField.yaml diff --git a/docs/schemas/StripeLinkResponseField.yaml b/schemas/OpenAPI/objects/StripeLinkResponseField.yaml similarity index 100% rename from docs/schemas/StripeLinkResponseField.yaml rename to schemas/OpenAPI/objects/StripeLinkResponseField.yaml diff --git a/docs/schemas/UserProfile.yaml b/schemas/OpenAPI/objects/UserProfile.yaml similarity index 94% rename from docs/schemas/UserProfile.yaml rename to schemas/OpenAPI/objects/UserProfile.yaml index 37544ae5..ea3fa697 100644 --- a/docs/schemas/UserProfile.yaml +++ b/schemas/OpenAPI/objects/UserProfile.yaml @@ -3,7 +3,7 @@ # REST Schema: UserProfile type: object -description: Parameters for a user's profile. +description: A user's profile. externalDocs: description: GQL Apollo Studio Schema Reference for the Profile object. url: "https://studio.apollographql.com/public/fixit/variant/current/schema/reference/objects/Profile" diff --git a/docs/schemas/UserRegistrationParams.yaml b/schemas/OpenAPI/objects/UserRegistrationParams.yaml similarity index 85% rename from docs/schemas/UserRegistrationParams.yaml rename to schemas/OpenAPI/objects/UserRegistrationParams.yaml index a2212847..7f0d3e9c 100644 --- a/docs/schemas/UserRegistrationParams.yaml +++ b/schemas/OpenAPI/objects/UserRegistrationParams.yaml @@ -10,7 +10,6 @@ allOf: handle: { $ref: "../open-api.yaml#/components/schemas/Handle" } email: { $ref: "../open-api.yaml#/components/schemas/Email" } phone: { $ref: "../open-api.yaml#/components/schemas/Phone" } - profile: { $ref: "../open-api.yaml#/components/schemas/UserProfileParams" } # <-- not required required: - handle - email diff --git a/docs/schemas/WorkOrder.yaml b/schemas/OpenAPI/objects/WorkOrder.yaml similarity index 69% rename from docs/schemas/WorkOrder.yaml rename to schemas/OpenAPI/objects/WorkOrder.yaml index b9d8e058..d139889c 100644 --- a/docs/schemas/WorkOrder.yaml +++ b/schemas/OpenAPI/objects/WorkOrder.yaml @@ -7,46 +7,39 @@ description: A pre-fetched WorkOrder object returned from a REST endpoint. externalDocs: description: GQL Apollo Studio Schema Reference for the WorkOrder object. url: "https://studio.apollographql.com/public/fixit/variant/current/schema/reference/objects/WorkOrder" -required: [id, createdBy, status, priority, location, createdAt, updatedAt] +required: [__typename, id, createdBy, status, priority, location, createdAt, updatedAt] properties: + __typename: + type: string + enum: [WorkOrder] + description: | + The object's GraphQL type name, `"WorkOrder"`, included to facilitate + writing pre-fetched objects into the front-end's Apollo Client cache. id: type: string description: The work order's ID. createdBy: type: object - description: The user or contact who created the work order. + description: The user who created the work order. required: [id] properties: id: type: string - description: The ID of the user or contact who created the work order. + description: The ID of the user who created the work order. assignedTo: type: - object - "null" - description: The user or contact to whom the work order is assigned. + description: The user to whom the work order is assigned. required: [id] properties: id: type: string - description: The ID of the user or contact to whom the work order is assigned. - status: - type: string - enum: [UNASSIGNED, ASSIGNED, IN_PROGRESS, DEFERRED, CANCELLED, COMPLETE] - description: The work order's status. - priority: - type: string - enum: [LOW, NORMAL, HIGH] - description: The work order's priority. - location: - $ref: "../open-api.yaml#/components/schemas/Location" - category: - type: - - string - - "null" - # prettier-ignore - enum: [null, DRYWALL, ELECTRICAL, FLOORING, GENERAL, HVAC, LANDSCAPING, MASONRY, PAINTING, PAVING, PEST, PLUMBING, ROOFING, TRASH, TURNOVER, WINDOWS] - description: The work order's category. + description: The ID of the user to whom the work order is assigned. + location: { $ref: "../open-api.yaml#/components/schemas/Location" } + status: { $ref: "../open-api.yaml#/components/schemas/WorkOrderStatus" } + priority: { $ref: "../open-api.yaml#/components/schemas/WorkOrderPriority" } + category: { $ref: "../open-api.yaml#/components/schemas/WorkOrderCategory" } description: type: - string diff --git a/docs/open-api.yaml b/schemas/OpenAPI/open-api.yaml similarity index 54% rename from docs/open-api.yaml rename to schemas/OpenAPI/open-api.yaml index 49ecdce3..0bda3719 100644 --- a/docs/open-api.yaml +++ b/schemas/OpenAPI/open-api.yaml @@ -1,10 +1,16 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml -openapi: "3.1.0" +openapi: 3.1.0 info: title: Fixit REST API - version: "2.0.1" - description: API for Fixit auth and user account management. + version: 2.2.0-next.1 + summary: OpenAPI Schema for the Fixit REST API. + description: | + This schema describes the Fixit REST API, which is used for Fixit + authentication and user account management. + ### Schema Links: + - [SwaggerHub Schema Ref](https://app.swaggerhub.com/apis/Nerdware/Fixit) + - [Fixit API GitHub Repo](https://github.com/Nerdware-LLC/fixit-api) termsOfService: "https://gofixit.app/tos" contact: name: Trevor Anderson @@ -14,7 +20,6 @@ info: name: Proprietary url: "https://raw.githubusercontent.com/Nerdware-LLC/fixit-api/main/LICENSE" servers: - - { url: "http://localhost:8080/api", description: Local Dev Server } - { url: "https://gofixit.app/api", description: Production API } - { url: "https://staging.gofixit.app/api", description: Staging API } tags: @@ -32,6 +37,8 @@ paths: "/auth/register": { $ref: "./endpoints/auth.register.yaml" } "/auth/token": { $ref: "./endpoints/auth.token.yaml" } "/auth/google-token": { $ref: "./endpoints/auth.google-token.yaml" } + "/auth/password-reset-init": { $ref: "./endpoints/auth.password-reset-init.yaml" } + "/auth/password-reset": { $ref: "./endpoints/auth.password-reset.yaml" } "/connect/account-link": { $ref: "./endpoints/connect.account-link.yaml" } "/connect/dashboard-link": { $ref: "./endpoints/connect.dashboard-link.yaml" } "/subscriptions/check-promo-code": { $ref: "./endpoints/subscriptions.check-promo-code.yaml" } @@ -44,16 +51,20 @@ components: CheckPromoCodeRequest: { $ref: "./requestBodies/CheckPromoCodeRequest.yaml" } GoogleTokenRequest: { $ref: "./requestBodies/GoogleTokenRequest.yaml" } LoginRequest: { $ref: "./requestBodies/LoginRequest.yaml" } + PasswordResetInitRequest: { $ref: "./requestBodies/PasswordResetInitRequest.yaml" } + PasswordResetRequest: { $ref: "./requestBodies/PasswordResetRequest.yaml" } RefreshAuthTokenRequest: { $ref: "./requestBodies/RefreshAuthTokenRequest.yaml" } StripeLinkRequest: { $ref: "./requestBodies/StripeLinkRequest.yaml" } UserRegistrationRequest: { $ref: "./requestBodies/UserRegistrationRequest.yaml" } # prettier-ignore responses: - 200AuthToken: { $ref: "./responses/200.AuthToken.yaml" } 200AuthTokenAndPreFetchedUserItems: { $ref: "./responses/200.AuthTokenAndPreFetchedUserItems.yaml" } - 200AuthTokenAndCheckoutCompletionInfo: { $ref: "./responses/200.AuthTokenAndCheckoutCompletionInfo.yaml" } 200CheckPromoCode: { $ref: "./responses/200.CheckPromoCode.yaml" } - 200StripeLink: { $ref: "./responses/200.StripeLink.yaml" } + 200OK: { $ref: "./responses/200.OK.yaml" } + 201AuthToken: { $ref: "./responses/201.AuthToken.yaml" } + 201AuthTokenAndCheckoutCompletionInfo: { $ref: "./responses/201.AuthTokenAndCheckoutCompletionInfo.yaml" } + 201StripeLink: { $ref: "./responses/201.StripeLink.yaml" } + 204NoContent: { $ref: "./responses/204.NoContent.yaml" } 400InvalidUserInput: { $ref: "./responses/400.InvalidUserInput.yaml" } 401AuthenticationRequired: { $ref: "./responses/401.AuthenticationRequired.yaml" } 402PaymentRequired: { $ref: "./responses/402.PaymentRequired.yaml" } @@ -64,40 +75,52 @@ components: # prettier-ignore schemas: # REQUEST-RELATED SCHEMAS: - UserRegistrationParams: { $ref: "./schemas/UserRegistrationParams.yaml" } - LoginParams: { $ref: "./schemas/LoginParams.yaml" } - LoginCredentials: { $ref: "./schemas/LoginCredentials.yaml" } - LocalLoginCredentials: { $ref: "./schemas/LoginCredentials.Local.yaml" } - GoogleOAuthLoginCredentials: { $ref: "./schemas/LoginCredentials.GoogleOAuth.yaml" } - GoogleIDTokenField: { $ref: "./schemas/GoogleIDTokenField.yaml" } - UserProfileParams: { $ref: "./schemas/UserProfileParams.yaml" } - ExpoPushTokenParam: { $ref: "./schemas/ExpoPushTokenParam.yaml" } + UserRegistrationParams: { $ref: "./objects/UserRegistrationParams.yaml" } + LoginParams: { $ref: "./objects/LoginParams.yaml" } + LoginCredentials: { $ref: "./objects/LoginCredentials.yaml" } + LocalLoginCredentials: { $ref: "./objects/LoginCredentials.Local.yaml" } + GoogleOAuthLoginCredentials: { $ref: "./objects/LoginCredentials.GoogleOAuth.yaml" } + GoogleIDTokenField: { $ref: "./objects/GoogleIDTokenField.yaml" } + ExpoPushTokenParam: { $ref: "./objects/ExpoPushTokenParam.yaml" } + PasswordResetInitParams: { $ref: "./objects/PasswordResetInitParams.yaml" } + PasswordResetParams: { $ref: "./objects/PasswordResetParams.yaml" } # RESPONSE-RELATED SCHEMAS: - AuthTokenResponseField: { $ref: "./schemas/AuthTokenResponseField.yaml" } - PreFetchedUserItemsResponseField: { $ref: "./schemas/PreFetchedUserItemsResponseField.yaml" } - PreFetchedUserItems: { $ref: "./schemas/PreFetchedUserItems.yaml" } - Contact: { $ref: "./schemas/Contact.yaml" } - Invoice: { $ref: "./schemas/Invoice.yaml" } - WorkOrder: { $ref: "./schemas/WorkOrder.yaml" } - PromoCodeInfoResponseField: { $ref: "./schemas/PromoCodeInfoResponseField.yaml" } - PromoCodeInfo: { $ref: "./schemas/PromoCodeInfo.yaml" } - CheckoutCompletionInfoResponseField: { $ref: "./schemas/CheckoutCompletionInfoResponseField.yaml" } - CheckoutCompletionInfo: { $ref: "./schemas/CheckoutCompletionInfo.yaml" } - StripeLinkResponseField: { $ref: "./schemas/StripeLinkResponseField.yaml" } - # OTHER SCHEMAS: - CspViolationReport: { $ref: "./schemas/CspViolationReport.yaml" } - Error: { $ref: "./schemas/Error.yaml" } - Location: { $ref: "./schemas/Location.yaml" } - SubscriptionPriceName: { $ref: "./schemas/SubscriptionPriceName.yaml" } - UserProfile: { $ref: "./schemas/UserProfile.yaml" } - # OBJECT PROPERTIES: + AuthTokenResponseField: { $ref: "./objects/AuthTokenResponseField.yaml" } + PreFetchedUserItemsResponseField: { $ref: "./objects/PreFetchedUserItemsResponseField.yaml" } + PreFetchedUserItems: { $ref: "./objects/PreFetchedUserItems.yaml" } + Contact: { $ref: "./objects/Contact.yaml" } + Invoice: { $ref: "./objects/Invoice.yaml" } + WorkOrder: { $ref: "./objects/WorkOrder.yaml" } + PromoCodeInfoResponseField: { $ref: "./objects/PromoCodeInfoResponseField.yaml" } + PromoCodeInfo: { $ref: "./objects/PromoCodeInfo.yaml" } + CheckoutCompletionInfoResponseField: { $ref: "./objects/CheckoutCompletionInfoResponseField.yaml" } + CheckoutCompletionInfo: { $ref: "./objects/CheckoutCompletionInfo.yaml" } + StripeLinkResponseField: { $ref: "./objects/StripeLinkResponseField.yaml" } + # OTHER OBJECT SCHEMAS: + AuthTokenPayload: { $ref: "./objects/AuthTokenPayload.yaml" } + AuthTokenPayloadSubscriptionInfo: { $ref: "./objects/AuthTokenPayloadSubscriptionInfo.yaml" } + AuthTokenPayloadStripeConnectAccountInfo: { $ref: "./objects/AuthTokenPayloadStripeConnectAccountInfo.yaml" } + CspViolationReport: { $ref: "./objects/CspViolationReport.yaml" } + Error: { $ref: "./objects/Error.yaml" } + Location: { $ref: "./objects/Location.yaml" } + UserProfile: { $ref: "./objects/UserProfile.yaml" } + # ENUM PROPERTIES: + InvoiceStatus: { $ref: "./enums/InvoiceStatus.yaml" } + SubscriptionPriceName: { $ref: "./enums/SubscriptionPriceName.yaml" } + SubscriptionStatus: { $ref: "./enums/SubscriptionStatus.yaml" } + WorkOrderCategory: { $ref: "./enums/WorkOrderCategory.yaml" } + WorkOrderPriority: { $ref: "./enums/WorkOrderPriority.yaml" } + WorkOrderStatus: { $ref: "./enums/WorkOrderStatus.yaml" } + # OTHER OBJECT PROPERTIES: CreatedAt: { $ref: "./objectProperties/createdAt.yaml" } Email: { $ref: "./objectProperties/email.yaml" } GoogleIDToken: { $ref: "./objectProperties/googleIDToken.yaml" } Handle: { $ref: "./objectProperties/handle.yaml" } Password: { $ref: "./objectProperties/password.yaml" } + PasswordResetToken: { $ref: "./objectProperties/passwordResetToken.yaml" } PaymentMethodID: { $ref: "./objectProperties/paymentMethodID.yaml" } Phone: { $ref: "./objectProperties/phone.yaml" } PromoCode: { $ref: "./objectProperties/promoCode.yaml" } ReturnURL: { $ref: "./objectProperties/returnURL.yaml" } + StripeCustomerID: { $ref: "./objectProperties/stripeCustomerID.yaml" } UpdatedAt: { $ref: "./objectProperties/updatedAt.yaml" } diff --git a/docs/requestBodies/CheckPromoCodeRequest.yaml b/schemas/OpenAPI/requestBodies/CheckPromoCodeRequest.yaml similarity index 100% rename from docs/requestBodies/CheckPromoCodeRequest.yaml rename to schemas/OpenAPI/requestBodies/CheckPromoCodeRequest.yaml diff --git a/docs/requestBodies/GoogleTokenRequest.yaml b/schemas/OpenAPI/requestBodies/GoogleTokenRequest.yaml similarity index 100% rename from docs/requestBodies/GoogleTokenRequest.yaml rename to schemas/OpenAPI/requestBodies/GoogleTokenRequest.yaml diff --git a/docs/requestBodies/LoginRequest.yaml b/schemas/OpenAPI/requestBodies/LoginRequest.yaml similarity index 100% rename from docs/requestBodies/LoginRequest.yaml rename to schemas/OpenAPI/requestBodies/LoginRequest.yaml diff --git a/schemas/OpenAPI/requestBodies/PasswordResetInitRequest.yaml b/schemas/OpenAPI/requestBodies/PasswordResetInitRequest.yaml new file mode 100644 index 00000000..08f419e5 --- /dev/null +++ b/schemas/OpenAPI/requestBodies/PasswordResetInitRequest.yaml @@ -0,0 +1,9 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/request-body + +# req.body for /auth/password-reset-init + +required: true +content: + application/json: + schema: + $ref: "../open-api.yaml#/components/schemas/PasswordResetInitParams" diff --git a/schemas/OpenAPI/requestBodies/PasswordResetRequest.yaml b/schemas/OpenAPI/requestBodies/PasswordResetRequest.yaml new file mode 100644 index 00000000..565b3cec --- /dev/null +++ b/schemas/OpenAPI/requestBodies/PasswordResetRequest.yaml @@ -0,0 +1,9 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/request-body + +# req.body for /auth/password-reset + +required: true +content: + application/json: + schema: + $ref: "../open-api.yaml#/components/schemas/PasswordResetParams" diff --git a/docs/requestBodies/RefreshAuthTokenRequest.yaml b/schemas/OpenAPI/requestBodies/RefreshAuthTokenRequest.yaml similarity index 100% rename from docs/requestBodies/RefreshAuthTokenRequest.yaml rename to schemas/OpenAPI/requestBodies/RefreshAuthTokenRequest.yaml diff --git a/docs/requestBodies/StripeLinkRequest.yaml b/schemas/OpenAPI/requestBodies/StripeLinkRequest.yaml similarity index 100% rename from docs/requestBodies/StripeLinkRequest.yaml rename to schemas/OpenAPI/requestBodies/StripeLinkRequest.yaml diff --git a/docs/requestBodies/UserRegistrationRequest.yaml b/schemas/OpenAPI/requestBodies/UserRegistrationRequest.yaml similarity index 100% rename from docs/requestBodies/UserRegistrationRequest.yaml rename to schemas/OpenAPI/requestBodies/UserRegistrationRequest.yaml diff --git a/docs/responses/200.AuthTokenAndPreFetchedUserItems.yaml b/schemas/OpenAPI/responses/200.AuthTokenAndPreFetchedUserItems.yaml similarity index 55% rename from docs/responses/200.AuthTokenAndPreFetchedUserItems.yaml rename to schemas/OpenAPI/responses/200.AuthTokenAndPreFetchedUserItems.yaml index b0b74158..9e587c0a 100644 --- a/docs/responses/200.AuthTokenAndPreFetchedUserItems.yaml +++ b/schemas/OpenAPI/responses/200.AuthTokenAndPreFetchedUserItems.yaml @@ -1,8 +1,11 @@ ## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response -# 200 response for successful authentication +description: | + [200 OK][mdn-docs] — Response for a successful authentication request. This + response includes an authentication token, as well as pre-fetched user items + to be stored in the client cache. -description: OK + [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 content: application/json: schema: diff --git a/docs/responses/200.CheckPromoCode.yaml b/schemas/OpenAPI/responses/200.CheckPromoCode.yaml similarity index 61% rename from docs/responses/200.CheckPromoCode.yaml rename to schemas/OpenAPI/responses/200.CheckPromoCode.yaml index 86f5299a..dbcf334e 100644 --- a/docs/responses/200.CheckPromoCode.yaml +++ b/schemas/OpenAPI/responses/200.CheckPromoCode.yaml @@ -1,8 +1,9 @@ ## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response -# 200 response for checking a promo code's validity +description: | + [200 OK][mdn-docs] — Response for checking a promo code's validity. -description: OK + [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 content: application/json: schema: diff --git a/schemas/OpenAPI/responses/200.OK.yaml b/schemas/OpenAPI/responses/200.OK.yaml new file mode 100644 index 00000000..854ada35 --- /dev/null +++ b/schemas/OpenAPI/responses/200.OK.yaml @@ -0,0 +1,7 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response + +description: | + [200][mdn-docs] — Generic success response for a request that was processed + successfully, and which includes a response body. + + [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 diff --git a/docs/responses/200.AuthToken.yaml b/schemas/OpenAPI/responses/201.AuthToken.yaml similarity index 61% rename from docs/responses/200.AuthToken.yaml rename to schemas/OpenAPI/responses/201.AuthToken.yaml index 78726488..82ceb54d 100644 --- a/docs/responses/200.AuthToken.yaml +++ b/schemas/OpenAPI/responses/201.AuthToken.yaml @@ -1,8 +1,9 @@ ## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response -# 200 response for successful user registration +description: | + [201 Created][mdn-docs] — Response for successful user registration. -description: OK + [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 content: application/json: schema: diff --git a/docs/responses/200.AuthTokenAndCheckoutCompletionInfo.yaml b/schemas/OpenAPI/responses/201.AuthTokenAndCheckoutCompletionInfo.yaml similarity index 68% rename from docs/responses/200.AuthTokenAndCheckoutCompletionInfo.yaml rename to schemas/OpenAPI/responses/201.AuthTokenAndCheckoutCompletionInfo.yaml index efceb693..373b096e 100644 --- a/docs/responses/200.AuthTokenAndCheckoutCompletionInfo.yaml +++ b/schemas/OpenAPI/responses/201.AuthTokenAndCheckoutCompletionInfo.yaml @@ -1,8 +1,9 @@ ## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response -# 200 response for payment-method submission +description: | + [201 Created][mdn-docs] — Response for successful payment submission. -description: OK + [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 content: application/json: schema: diff --git a/docs/responses/200.StripeLink.yaml b/schemas/OpenAPI/responses/201.StripeLink.yaml similarity index 60% rename from docs/responses/200.StripeLink.yaml rename to schemas/OpenAPI/responses/201.StripeLink.yaml index a3a61551..4e191076 100644 --- a/docs/responses/200.StripeLink.yaml +++ b/schemas/OpenAPI/responses/201.StripeLink.yaml @@ -1,8 +1,9 @@ ## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response -# 200 response for successfully obtaining a `stripeLink` +description: | + [201 Created][mdn-docs] — Response for successful creation of a Stripe link. -description: OK + [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 content: application/json: schema: diff --git a/schemas/OpenAPI/responses/204.NoContent.yaml b/schemas/OpenAPI/responses/204.NoContent.yaml new file mode 100644 index 00000000..9a9e344a --- /dev/null +++ b/schemas/OpenAPI/responses/204.NoContent.yaml @@ -0,0 +1,7 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response + +description: | + [204 No Content][mdn-docs] — Generic success response which does not + include a response body. + + [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 diff --git a/docs/responses/400.InvalidUserInput.yaml b/schemas/OpenAPI/responses/400.InvalidUserInput.yaml similarity index 62% rename from docs/responses/400.InvalidUserInput.yaml rename to schemas/OpenAPI/responses/400.InvalidUserInput.yaml index f22e459a..4483fabd 100644 --- a/docs/responses/400.InvalidUserInput.yaml +++ b/schemas/OpenAPI/responses/400.InvalidUserInput.yaml @@ -1,8 +1,9 @@ ## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response -# 400 response +description: | + [400 Bad Request][mdn-docs] — Invalid user input. -description: "(400) Invalid user input" + [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 content: application/json: schema: diff --git a/docs/responses/401.AuthenticationRequired.yaml b/schemas/OpenAPI/responses/401.AuthenticationRequired.yaml similarity index 61% rename from docs/responses/401.AuthenticationRequired.yaml rename to schemas/OpenAPI/responses/401.AuthenticationRequired.yaml index 10cfd2b4..a0dca416 100644 --- a/docs/responses/401.AuthenticationRequired.yaml +++ b/schemas/OpenAPI/responses/401.AuthenticationRequired.yaml @@ -1,8 +1,9 @@ ## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response -# 401 response +description: | + [401 Unauthorized][mdn-docs] — Authentication required. -description: "(401) Authentication required" + [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 content: application/json: schema: diff --git a/docs/responses/402.PaymentRequired.yaml b/schemas/OpenAPI/responses/402.PaymentRequired.yaml similarity index 81% rename from docs/responses/402.PaymentRequired.yaml rename to schemas/OpenAPI/responses/402.PaymentRequired.yaml index 32ce1b5b..b3f0d3cd 100644 --- a/docs/responses/402.PaymentRequired.yaml +++ b/schemas/OpenAPI/responses/402.PaymentRequired.yaml @@ -1,8 +1,7 @@ ## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response -# 402 response - -description: "(402) Payment required" +description: | + [402 Payment Required] content: application/json: schema: diff --git a/docs/responses/403.Forbidden.yaml b/schemas/OpenAPI/responses/403.Forbidden.yaml similarity index 71% rename from docs/responses/403.Forbidden.yaml rename to schemas/OpenAPI/responses/403.Forbidden.yaml index 8d2765f1..9421d771 100644 --- a/docs/responses/403.Forbidden.yaml +++ b/schemas/OpenAPI/responses/403.Forbidden.yaml @@ -1,9 +1,7 @@ ## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response -# 403 response - description: | - (403) Forbidden — the requesting user is not authorized to perform this action + [403 Forbidden] The requesting user is not authorized to perform this action. content: application/json: schema: diff --git a/docs/responses/404.ResourceNotFound.yaml b/schemas/OpenAPI/responses/404.ResourceNotFound.yaml similarity index 75% rename from docs/responses/404.ResourceNotFound.yaml rename to schemas/OpenAPI/responses/404.ResourceNotFound.yaml index 5cb53425..72eb8ae8 100644 --- a/docs/responses/404.ResourceNotFound.yaml +++ b/schemas/OpenAPI/responses/404.ResourceNotFound.yaml @@ -1,8 +1,7 @@ ## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response -# 404 response - -description: "(404) Requested resource not found" +description: | + [404 Not Found] The requested resource could not be found. content: application/json: schema: diff --git a/docs/responses/5xx.InternalServerError.yaml b/schemas/OpenAPI/responses/5xx.InternalServerError.yaml similarity index 64% rename from docs/responses/5xx.InternalServerError.yaml rename to schemas/OpenAPI/responses/5xx.InternalServerError.yaml index 665ec543..af6f9d1c 100644 --- a/docs/responses/5xx.InternalServerError.yaml +++ b/schemas/OpenAPI/responses/5xx.InternalServerError.yaml @@ -1,8 +1,9 @@ ## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response -# 5XX response +description: | + [5XX Internal Server Error][mdn-docs] -description: "(5XX) Internal server error" + [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 content: application/json: schema: diff --git a/schemas/OpenAPI/responses/default.UnexpectedResponse.yaml b/schemas/OpenAPI/responses/default.UnexpectedResponse.yaml new file mode 100644 index 00000000..8c5cd8ee --- /dev/null +++ b/schemas/OpenAPI/responses/default.UnexpectedResponse.yaml @@ -0,0 +1,6 @@ +## yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.yaml#/$defs/response + +description: | + [Unexpected Response] The server encountered an unexpected condition that + prevented it from fulfilling the request. This fallback applies if no defined + response status codes match the response. diff --git a/docs/securitySchemes/JwtBearerAuth.yaml b/schemas/OpenAPI/securitySchemes/JwtBearerAuth.yaml similarity index 100% rename from docs/securitySchemes/JwtBearerAuth.yaml rename to schemas/OpenAPI/securitySchemes/JwtBearerAuth.yaml diff --git a/scripts/cicd.publish-schema-graphql.sh b/scripts/cicd.publish-schema-graphql.sh new file mode 100644 index 00000000..44ff404c --- /dev/null +++ b/scripts/cicd.publish-schema-graphql.sh @@ -0,0 +1,205 @@ +#!/usr/bin/env bash +############################################################################### +readonly script_name='Publish Fixit GraphQL Schema Changes' +readonly script_filename="$(basename "${BASH_SOURCE[0]}")" + +# Script constants: +readonly typedefs_src='src/graphql/typeDefs.ts' +readonly graph_name='fixit' +readonly default_variant='current' + +readonly script_help=" +SCRIPT: $script_name + + This script updates the $graph_name GraphQL schema in Apollo Studio using the + provided '--variant' (default: '$default_variant'). + + An up-to-date schema is generated using type-defs located in the project's + source code (see '$typedefs_src'). The schema is then validated + and published using Apollo's Rover CLI. + +USAGE: scripts/$script_filename [OPTIONS] + +OPTIONS: + --variant= The graph variant to use when validating and publishing the + schema. If provided, must be one of 'prod', 'staging', or + 'current' (default: '$default_variant'). + --dry-run Only generate the local schema file, but do not publish it. + --debug Prevent removal of temporary files on exit (default: false). + -h, --help Display this help message and exit. +" +############################################################################### +# UTIL FUNCTIONS + +# log_info: print args to stdout (ANSI: \e[96m is light-cyan text, \e[0m is reset) +function log_info() { printf '\e[96m%b\e[0m\n' "${@}"; } + +# throw_error: print err-msg args to stdout+stderr and exit (ANSI: \e[31m is red text) +function throw_error() { + printf >&2 '\e[31m%b\e[0m\n' "🚨 ERROR: $1" "${@:2}" '(EXIT 1)' + exit 1 +} + +############################################################################### +# PARSE SCRIPT ARGS/OPTIONS + +# If a 'help' flag was provided, log the help message and exit +[[ "${*}" =~ (-h|help) ]] && echo "$script_help" && exit 0 + +readonly debug="$([[ "${*}" == *--debug* ]] && echo true || echo false)" + +readonly dry_run="$([[ "${*}" == *--dry-run* ]] && echo true || echo false)" + +readonly variant_arg="$(grep -oPm1 '(?<=--variant(=|\s))\S+' <<<"$*")" +readonly variant="${variant_arg:-$default_variant}" + +############################################################################### +# SCRIPT ACTION FUNCTIONS + +function ensure_graph_ref_is_valid() { + if [[ ! "$variant" =~ ^(prod|staging|current)$ ]]; then + throw_error "Invalid --variant value. Must be one of 'prod', 'staging', or 'current'." + fi + + # If variant is valid, set global var 'graph_ref' + readonly graph_ref="$graph_name@$variant" +} + +function ensure_npx_cmd_is_present() { + if ! type npx 1>/dev/null; then + # Try invoking `nvm` to make it available + type nvm 1>/dev/null && nvm use 1>/dev/null + # If `npx` is still not available, throw an error + ! type npx && throw_error \ + 'Unable to proceed with script execution — npx command not found.' + fi +} + +function setup_tmp_dir() { + # Global vars: + readonly tmp_dir="$(basename "$( + mktemp --tmpdir="$PWD" -d "tmp.${script_filename%.sh}.XXX" || + throw_error 'Failed to create temporary directory.' + )")" + + # TRAP: on EXIT, remove the tmp dir unless --debug flag is present + function rm_tmp_unless_debug() { + if [ "$debug" == 'true' ]; then + log_info "[DEBUG MODE]: Not removing temporary directory \e[0m$tmp_dir" + else + rm -rf "$tmp_dir" + fi + } + + trap rm_tmp_unless_debug EXIT +} + +function generate_local_gql_schema_file() { + log_info "Generating GraphQL schema file '\e[0m./schema.graphql\e[96m' ..." + + # Global vars: + readonly tsc_outdir="$tmp_dir/tsc-outdir" + readonly schema_file="$tmp_dir/schema.graphql" + + # Now to make the GQL schema, the TS files must first be converted to JS: + npx tsc \ + --outDir "$tsc_outdir" \ + --target ESNext \ + --module ESNext \ + --moduleResolution Node \ + "$typedefs_src" + + # Now make the GQL schema from the generated JS files: + node --input-type=module -e " + import fs from 'node:fs'; + import { mergeTypeDefs } from '@graphql-tools/merge'; + import { print } from 'graphql'; + import { typeDefs } from './$tsc_outdir/typeDefs.js'; + + // The type-defs are exported as an array of GraphQL AST nodes + const typeDefsMerged = mergeTypeDefs(typeDefs); + + let sdlString = print(typeDefsMerged); + + /******************************* SDL Formatting ******************************* + * Before writing the SDL to disk, the string is formatted as follows: + * + * 1. Single-double-quote docstring boundaries are converted to triple-double-quotes. + * 2. Single-line docstrings longer than 70 characters are line-wrapped. + * 3. Docstrings which are immediately preceded by a field definition on the line + * above are prefixed by a newline to ensure an empty line exists between the two. + * 4. The root 'schema' block is removed. + * + * Why do this? To ensure the SDL is formatted in the same manner as if it were + * fetched/downloaded from Apollo Studio, thereby ensuring consistency and making + * it easier to compare/diff the schema from one version to the next. + */ + + // Regex op 1: convert all double-quote docstring boundaries to triple-double-quotes. + // ^ Asserts position at the start of a line. + // (\u0020*?) The first group captures any leading space chars before a docstring. + // \" Matches exactly one double quote character. + // ([^\"\n]+?) The second group captures the text between the double-quotes. + // \" Matches exactly one double quote character. + // $ Asserts position at the end of a line. + sdlString = sdlString.replaceAll(/^(\u0020*?)\"([^\"\n]+?)\"$/gm, '\$1\"\"\"\$2\"\"\"'); + + // Regex op 2: line-wrap single-line docstrings longer than 70 characters. + // ^ Asserts position at the start of a line. + // (\u0020*?) The first group captures any leading whitespace before a docstring. + // \"\"\" Matches exactly three double quote characters. + // ([^\"\n]{70,}?) The second group captures a single line of text that's 70+ chars long. + // \"\"\" Matches exactly three double quote characters. + // $ Asserts position at the end of a line. + sdlString = sdlString.replaceAll(/^(\u0020*?)\"\"\"([^\"\n]{70,}?)\"\"\"$/gm, '\$1\"\"\"\n\$1\$2\n\$1\"\"\"'); + + // Regex op 3: ensure exactly 1 empty line exists between docstrings and preceding field defs. + // (?<=:\u0020[a-zA-Z0-9=!\[\]\u0020]+?\n) Positive lookbehind for a field definition (e.g. 'field: [Type!]'). + // (^\u0020*?\"\"\"[^\"]+) Captures a docstring. + sdlString = sdlString.replaceAll(/(?<=:\u0020[a-zA-Z0-9=!\[\]\u0020]+?\n)(^\u0020*?\"\"\"[^\"]+)/gm, '\n\$1'); + + // Regex op 4: remove the root 'schema' block + // \n^schema\u0020{[^]* Matches the 'schema' block and everything after it. + sdlString = sdlString.replace(/\n^schema\u0020{[^]*/m, ''); + + fs.writeFileSync('$schema_file', sdlString);" + + # Throw error if the schema file was not generated + [[ $? != 0 || ! -f "$schema_file" ]] && throw_error 'Failed to generate GraphQL schema file.' + + log_info 'GraphQL schema file generated successfully! 🚀\n' +} + +function validate_local_gql_schema() { + log_info "Validating the GraphQL schema for graph '\e[0m$graph_ref\e[96m' ..." + + if npx rover graph check "$graph_ref" --schema "$schema_file" &>/dev/null; then + log_info 'The GraphQL schema is valid! 🎉\n' + else + throw_error 'The GraphQL schema is invalid.' + fi +} + +function publish_schema_unless_dry_run() { + if [ "$dry_run" != 'true' ]; then + log_info "Publishing the GraphQL schema for graph '\e[0m$graph_ref\e[96m' to Apollo Studio ..." + + npx rover graph publish "$graph_ref" --schema "$schema_file" + else + log_info 'Dry-run mode enabled. Skipping schema publication.' + fi +} + +############################################################################### +# SCRIPT EXECUTION + +log_info "[Starting Script: $script_name]\n" + +ensure_graph_ref_is_valid +ensure_npx_cmd_is_present +setup_tmp_dir +generate_local_gql_schema_file +validate_local_gql_schema +publish_schema_unless_dry_run + +############################################################################### diff --git a/scripts/cicd.publish-schema-open-api.sh b/scripts/cicd.publish-schema-open-api.sh new file mode 100644 index 00000000..e29bbf01 --- /dev/null +++ b/scripts/cicd.publish-schema-open-api.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash +############################################################################### +readonly script_name='Publish Fixit OpenAPI Schema Changes' +readonly script_filename="$(basename "${BASH_SOURCE[0]}")" + +# Script constants: +readonly schema_file='schemas/OpenAPI/open-api.yaml' + +readonly script_help=" +SCRIPT: $script_name + + This script updates the Fixit OpenAPI schema in SwaggerHub. + + Schema Input: $schema_file + +USAGE: scripts/$script_filename [OPTIONS] + +OPTIONS: + --version= The schema version to use for publishing the schema (required). + --update Update the schema if the version already exists. + --setdefault Publish the schema as the new default version (default: false). + Use this flag when publishing from a production release. + --dry-run Run the script, but do not publish the schema to SwaggerHub. + --debug Prevent removal of temporary files on exit (default: false). + -h, --help Display this help message and exit. +" +############################################################################### +# UTIL FUNCTIONS + +# log_info: print args to stdout (ANSI: \e[96m is light-cyan text, \e[0m is reset) +function log_info() { printf '\e[96m%b\e[0m\n' "${@}"; } + +# throw_error: print err-msg args to stdout+stderr and exit (ANSI: \e[31m is red text) +function throw_error() { + printf >&2 '\e[31m%b\e[0m\n' "🚨 ERROR: $1" "${@:2}" '(EXIT 1)' + exit 1 +} + +############################################################################### +# PARSE SCRIPT ARGS/OPTIONS + +# If a 'help' flag was provided, log the help message and exit +[[ "${*}" =~ (-h|help) ]] && echo "$script_help" && exit 0 + +readonly debug="$([[ "${*}" == *--debug* ]] && echo true || echo false)" + +readonly dry_run="$([[ "${*}" == *--dry-run* ]] && echo true || echo false)" + +readonly set_default="$([[ "${*}" == *--setdefault* ]] && echo true || echo false)" + +readonly update_if_exists="$([[ "${*}" == *--update* ]] && echo true || echo false)" + +readonly version_arg="$(grep -oPm1 '(?<=--version(=|\s))\S+' <<<"$*")" + +############################################################################### +# SCRIPT ACTION FUNCTIONS + +function ensure_version_arg_is_valid() { + [ -z "$version_arg" ] && + throw_error "Invalid --version value. Must be a valid git ref." + + # If version is valid, set global var 'version' + readonly version="$version_arg" +} + +function ensure_npx_cmd_is_present() { + if ! type npx 1>/dev/null; then + # Try invoking `nvm` to make it available + type nvm 1>/dev/null && nvm use 1>/dev/null + # If `npx` is still not available, throw an error + ! type npx && + throw_error 'Unable to proceed with script execution — npx command not found.' + fi +} + +function validate_openapi_schema() { + log_info 'Validating the OpenAPI schema ...' + + ! npx redocly lint "$schema_file" 1>/dev/null && + throw_error 'The OpenAPI schema is invalid.' +} + +function setup_tmp_dir() { + # Global vars: + readonly tmp_dir="$(basename "$( + mktemp --tmpdir="$PWD" -d "tmp.${script_filename%.sh}.XXX" || + throw_error 'Failed to create temporary directory.' + )")" + + # TRAP: on EXIT, remove the tmp dir unless --debug flag is present + function rm_tmp_unless_debug() { + if [ "$debug" == 'true' ]; then + log_info "[DEBUG MODE]: Not removing temporary directory \e[0m$tmp_dir" + else + rm -rf "$tmp_dir" + fi + } + + trap rm_tmp_unless_debug EXIT +} + +function create_bundled_schema_file() { + log_info 'Bundling the schema for publication ...' + + # Global vars: + readonly tmp_bundled_schema_file="$tmp_dir/open-api-bundled.yaml" + + npx redocly bundle $schema_file --output "$tmp_bundled_schema_file" 1>/dev/null + + # Throw error if the bundled-schema-file was not generated + [[ $? != 0 || ! -f "$tmp_bundled_schema_file" ]] && + throw_error 'Failed to create schema bundle for publication.' +} + +function publish_schema_unless_dry_run() { + # First, check if dry-run mode is enabled + if [ "$dry_run" == 'true' ]; then + log_info 'Dry-run mode enabled. Skipping schema publication.' + return 0 + fi + + log_info "Publishing the OpenAPI schema to SwaggerHub ..." + + # SwaggerHub CLI args: + local schema_ref="Nerdware/Fixit/$version" + local swaggerhub_cli_args=( + "$schema_ref" + "--file=$tmp_bundled_schema_file" + '--visibility=public' + ) + + # SwaggerHub CLI args for updating the default version: + [ "$set_default" == 'true' ] && swaggerhub_cli_args+=( + '--setdefault' + '--published=publish' + ) + + # Try to create a new schema version, capture stdout+stderr + local output="$(npx swaggerhub api:create "${swaggerhub_cli_args[@]}" 2>&1)" + + # If the 'api:create' operation succeeded, log success msg and end script + if [[ "$output" != *Error* ]]; then + log_info "New schema version '$version' published successfully! 🚀" + return 0 + fi + + # If it failed bc of anything other than "the version already exists", throw error + [[ "$output" != *"API version '$schema_ref' already exists"* ]] && + throw_error 'Failed to publish the schema to SwaggerHub.' "$output" + + # If the version already exists, but "update-mode" is not enabled, throw error + [ "$update_if_exists" != 'true' ] && throw_error \ + "Schema version '$version' already exists, but the --update flag was not provided." \ + 'Use the --update flag to update an existing version.' + + log_info "Schema version '$version' already exists." \ + 'Attempting to update the existing version ...' + + # Try again using 'api:update' instead of 'api:create' + npx swaggerhub api:update "${swaggerhub_cli_args[@]}" +} + +############################################################################### +# SCRIPT EXECUTION + +log_info "\n[Starting Script: $script_name]\n" + +ensure_version_arg_is_valid +ensure_npx_cmd_is_present +validate_openapi_schema +setup_tmp_dir +create_bundled_schema_file +publish_schema_unless_dry_run + +############################################################################### diff --git a/scripts/codegen.open-api.sh b/scripts/codegen.open-api.sh deleted file mode 100644 index 173af654..00000000 --- a/scripts/codegen.open-api.sh +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env bash -#################################################################################### -# This script validates the OpenAPI Schema and uses it to generate types. -# -# SCRIPT FLAGS: -# -# --update-swaggerhub: Update the schema in SwaggerHub. -# PRE-REQUISITES: -# 1. The SWAGGERHUB_API_KEY environment variable must be available. -# 2. The "version" in package.json must not reflect a pre-release version. -# -# --create-new-version: Create a new version of the schema in SwaggerHub. -# PRE-REQUISITES: -# 1. The '--update-swaggerhub' flag must be present. -# 2. The SWAGGERHUB_API_KEY environment variable must be available. -# 3. The "version" in package.json must not reflect a pre-release version. -#################################################################################### - -# Util function to "throw" an error and exit -throw_error() { echo "ERROR: $1 (EXIT 1)"; exit 1; }; - -# Ensure the root OAS schema and package.json are present -[ ! -f docs/open-api.yaml ] && throw_error "OpenAPI Schema not found" -[ ! -f package.json ] && throw_error "package.json not found" - -# Ensure the `npx` command is available -if ! type npx 1>/dev/null; then - # Try invoking `nvm` to make it available - type nvm 1>/dev/null && nvm use 1>/dev/null - # If `npx` is still not available, throw an error - ! type npx && throw_error "npx command not found" -fi - -# Ensure the OAS schema is valid -! npx swagger-cli validate docs/open-api.yaml && \ - throw_error "OpenAPI Schema validation failed" - -# Read the project "version" from package.json -project_version="$( jq '.version' ./package.json )" - -# Ascertain whether SwaggerHub should be updated: -if [[ -n "$SWAGGERHUB_API_KEY" && \ - "$project_version" != *-* && \ - "${*}" == *--update-swaggerhub* ]]; then - update_swaggerhub=true -else - update_swaggerhub=false -fi - -# Update the schema in SwaggerHub if all conditions are met -if [ $update_swaggerhub == true ]; then - - # If running within a CI env, this script creates a new schema version - if [[ "${*}" == *--create-new-version* ]]; then - create_new_version=true - else - create_new_version=false - fi - - # If running within a CI env, configure behavior to Ascertain whether a new schema version should be created - - # If create_new_version, first ensure the schema version = project version - if [ $create_new_version == true ]; then - sed -i \ - "s/version: .*/version: $project_version/" \ - docs/open-api.yaml - fi - - # Create a bundled schema file for SwaggerHub - npx swagger-cli bundle docs/open-api.yaml \ - --outfile docs/open-api.bundled.yaml \ - --type yaml - - # Ensure the bundled schema file was created successfully - [ ! -f docs/open-api.bundled.yaml ] && \ - throw_error "Failed to create bundled schema file" - - # Set the SwaggerHub CLI flags (common to both UPDATE and CREATE ops) - swaggerhub_cli_shared_flags=( - --file=docs/open-api.bundled.yaml - --visibility=public - --setdefault - ) - - # Ascertain whether the SwaggerHub op should be UPDATE or CREATE: - if [ $create_new_version == true ]; then - # Create a new schema version - npx swaggerhub api:create Nerdware/Fixit "${swaggerhub_cli_shared_flags[@]}" - else - # Update an existing schema version - npx swaggerhub api:update Nerdware/Fixit "${swaggerhub_cli_shared_flags[@]}" - fi - - # Cleanup - remove the bundled schema file - rm docs/open-api.bundled.yaml -fi - -# Update OpenAPI types using NodeJS API from openapi-typescript to fix `Date` types. -# (Their CLI does not convert `format: date-time` values to `Date` types) - -node --input-type=module -e " -import fs from 'node:fs'; -import ts from 'typescript'; -import openapiTS, { astToString } from 'openapi-typescript'; - -const DATE = ts.factory.createIdentifier('Date'); -const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull()); - -const ast = await openapiTS( - new URL('file://$PWD/docs/open-api.yaml'), - { - transform(schemaObject, metadata) { - if (schemaObject.format === 'date-time') { - return Array.isArray(schemaObject.type) && schemaObject.type.includes('null') - ? ts.factory.createUnionTypeNode([DATE, NULL]) - : DATE; - } - }, - } -); - -const tsFileContents = \`\ -/** - * This file was auto-generated by openapi-typescript. - * Do not make direct changes to the file. - */ - -\${astToString(ast)} -\`; - -fs.writeFileSync('src/types/__codegen__/open-api.ts', tsFileContents);" - -############################################################################### diff --git a/scripts/codegen.types-graphql.sh b/scripts/codegen.types-graphql.sh new file mode 100644 index 00000000..f469ee43 --- /dev/null +++ b/scripts/codegen.types-graphql.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +############################################################################### +readonly script_name='Fixit GraphQL Types Codegen' +readonly script_filename="$(basename "${BASH_SOURCE[0]}")" + +# Script constants: +readonly schema_files='src/graphql/**/typeDefs.ts' +readonly types_output='src/types/__codegen__/graphql.ts' + +readonly script_help=" +SCRIPT: $script_name + + This script creates TypeScript types from the Fixit GraphQL schema. + + Schema Input: $schema_files + Types Output: $types_output + +USAGE: scripts/$script_filename [OPTIONS] + +OPTIONS: + -h, --help Display this help message and exit. +" +############################################################################### +# PARSE SCRIPT ARGS/OPTIONS + +# If a 'help' flag was provided, log the help message and exit +[[ "${*}" =~ (-h|help) ]] && echo "$script_help" && exit 0 + +############################################################################### +# UTIL FUNCTIONS + +# log_info: print args to stdout (ANSI: \e[96m is light-cyan text, \e[0m is reset) +function log_info() { printf '\e[96m%b\e[0m\n' "${@}"; } + +# throw_error: print err-msg args to stdout+stderr and exit (ANSI: \e[31m is red text) +function throw_error() { + printf >&2 '\e[31m%b\e[0m\n' "🚨 ERROR: $1" "${@:2}" '(EXIT 1)' + exit 1 +} + +############################################################################### +# SCRIPT ACTION FUNCTIONS + +function ensure_npx_cmd_is_present() { + if ! type npx 1>/dev/null; then + # Try invoking `nvm` to make it available + type nvm 1>/dev/null && nvm use 1>/dev/null + # If `npx` is still not available, throw an error + ! type npx && throw_error \ + 'Unable to proceed with script execution — npx command not found.' + fi +} + +function generate_graphql_ts_types() { + log_info 'Generating GraphQL types ...' + + NODE_OPTIONS='-r ts-node/register --loader ./loader.js --no-warnings' \ + npx graphql-codegen-esm --config codegen.ts + + # Log the result of the OpenAPI codegen operation + if [ $? != 0 ]; then + throw_error 'Failed to generate GraphQL TypeScript types.' + else + log_info 'GraphQL TypeScript types generated successfully! 🚀' + fi +} + +############################################################################### +# SCRIPT EXECUTION + +log_info "[Starting Script: $script_name]\n" + +ensure_npx_cmd_is_present +generate_graphql_ts_types + +############################################################################### diff --git a/scripts/codegen.types-open-api.sh b/scripts/codegen.types-open-api.sh new file mode 100644 index 00000000..79a9dd78 --- /dev/null +++ b/scripts/codegen.types-open-api.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +############################################################################### +readonly script_name='Fixit OpenAPI Types Codegen' +readonly script_filename="$(basename "${BASH_SOURCE[0]}")" + +# Script constants: +readonly schema_file='schemas/OpenAPI/open-api.yaml' +readonly types_output='src/types/__codegen__/open-api.ts' + +readonly script_help=" +SCRIPT: $script_name + + This script creates TypeScript types from the Fixit OpenAPI schema file. + + Schema Input: $schema_file + Types Output: $types_output + +USAGE: scripts/$script_filename [OPTIONS] + +OPTIONS: + -h, --help Display this help message and exit. +" +############################################################################### +# PARSE SCRIPT ARGS/OPTIONS + +# If a 'help' flag was provided, log the help message and exit +[[ "${*}" =~ (-h|help) ]] && echo "$script_help" && exit 0 + +############################################################################### +# UTIL FUNCTIONS + +# log_info: print args to stdout (ANSI: \e[96m is light-cyan text, \e[0m is reset) +function log_info() { printf '\e[96m%b\e[0m\n' "${@}"; } + +# throw_error: print err-msg args to stdout+stderr and exit (ANSI: \e[31m is red text) +function throw_error() { + printf >&2 '\e[31m%b\e[0m\n' "🚨 ERROR: $1" "${@:2}" '(EXIT 1)' + exit 1 +} + +############################################################################### +# SCRIPT ACTION FUNCTIONS + +function ensure_npx_cmd_is_present() { + if ! type npx 1>/dev/null; then + # Try invoking `nvm` to make it available + type nvm 1>/dev/null && nvm use 1>/dev/null + # If `npx` is still not available, throw an error + ! type npx && throw_error \ + 'Unable to proceed with script execution — npx command not found.' + fi +} + +function validate_openapi_schema() { + log_info 'Validating the OpenAPI schema ...' + + ! npx redocly lint "$schema_file" 1>/dev/null && + throw_error 'The OpenAPI schema is invalid.' + + log_info 'The OpenAPI schema is valid! 🎉\n' +} + +function generate_openapi_ts_types() { + log_info 'Generating OpenAPI TypeScript types ...' + + local schema_file_version="$(grep -oPm1 '(?<=version:\s)[a-zA-Z0-9.-]+' "$schema_file")" + + # Update OpenAPI types using NodeJS API from openapi-typescript to fix `Date` types. + # (Their CLI does not convert `format: date-time` values to `Date` types) + + node --input-type=module -e " + import fs from 'node:fs'; + import ts from 'typescript'; + import openapiTS, { astToString } from 'openapi-typescript'; + + const DATE = ts.factory.createTypeReferenceNode('Date'); + const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull()); + const UNION_DATE_NULL = ts.factory.createUnionTypeNode([DATE, NULL]); + + const ast = await openapiTS( + new URL('file://$PWD/$schema_file'), + { + transform(schemaObject, metadata) { + if (schemaObject.format === 'date-time') { + return Array.isArray(schemaObject.type) && schemaObject.type.includes('null') + ? UNION_DATE_NULL + : DATE; + } + } + }, + ); + + const tsFileContents = \`\ + /** + * Fixit OpenAPI Schema Types + * + * DO NOT MAKE DIRECT CHANGES TO THIS FILE. + * + * This file was auto-generated using schema version: \\\`$schema_file_version\\\` + */ + + \${astToString(ast)} + \`.replaceAll(/^\t{0,2}/gm, ''); // <-- Removes leading tabs + + fs.writeFileSync('$types_output', tsFileContents);" + + # Log the result of the OpenAPI codegen operation + if [ $? != 0 ]; then + throw_error 'Failed to generate OpenAPI TypeScript types.' + else + log_info 'OpenAPI TypeScript types generated successfully! 🚀' + fi +} + +############################################################################### +# SCRIPT EXECUTION + +log_info "\n[Starting Script: $script_name]\n" + +ensure_npx_cmd_is_present +validate_openapi_schema +generate_openapi_ts_types + +############################################################################### diff --git a/src/__mocks__/apolloServer.ts b/src/__mocks__/apolloServer.ts index acf5a280..b8af9129 100644 --- a/src/__mocks__/apolloServer.ts +++ b/src/__mocks__/apolloServer.ts @@ -1,8 +1,14 @@ import { ApolloServer } from "@apollo/server"; -import { ApolloServerPluginLandingPageDisabled } from "@apollo/server/plugin/disabled"; +import { + ApolloServerPluginLandingPageDisabled, + ApolloServerPluginSchemaReportingDisabled, + ApolloServerPluginUsageReportingDisabled, +} from "@apollo/server/plugin/disabled"; +import { ApolloServerPluginDrainHttpServer } from "@apollo/server/plugin/drainHttpServer"; import { addMocksToSchema } from "@graphql-tools/mock"; -import { fixitSchema } from "@/graphql/schema.js"; -import type { ApolloServerResolverContext } from "@/apolloServer.js"; +import { formatApolloError } from "@/graphql/GraphQLError/helpers.js"; +import { schema } from "@/graphql/schema.js"; +import type { ApolloServerContext, ApolloServerWithContext } from "@/apolloServer.js"; /** * ### MOCK APOLLO SERVER @@ -13,18 +19,33 @@ import type { ApolloServerResolverContext } from "@/apolloServer.js"; * - https://www.apollographql.com/docs/apollo-server/testing/mocking * - https://www.apollographql.com/docs/apollo-server/testing/testing */ -export const apolloServer = new ApolloServer({ +export const apolloServer = new ApolloServer({ schema: addMocksToSchema({ - schema: fixitSchema, + schema, mocks: { // Manually created mocks can go here }, preserveResolvers: true, }), + formatError: formatApolloError, csrfPrevention: false, introspection: false, - plugins: [ApolloServerPluginLandingPageDisabled()], -}); + includeStacktraceInErrorResponses: true, + status400ForVariableCoercionErrors: true, +}) as ApolloServerWithContext; -// Run required init logic for integrating with Express -await apolloServer.start(); +apolloServer.configurePlugins = async ({ httpServer }) => { + apolloServer.addPlugin(ApolloServerPluginDrainHttpServer({ httpServer })); + // Disable the landing-page and all reporting plugins: + apolloServer.addPlugin(ApolloServerPluginLandingPageDisabled()); + apolloServer.addPlugin(ApolloServerPluginSchemaReportingDisabled()); + apolloServer.addPlugin(ApolloServerPluginUsageReportingDisabled()); + // No dynamic import needed - return explicit Promise + return Promise.resolve(); +}; + +// Export the actual `getAuthenticatedApolloContext` fn: +const { getAuthenticatedApolloContext } = + await vi.importActual("@/apolloServer.js"); + +export { getAuthenticatedApolloContext }; diff --git a/src/apolloServer.ts b/src/apolloServer.ts index c1cdd215..d25894d6 100644 --- a/src/apolloServer.ts +++ b/src/apolloServer.ts @@ -1,28 +1,112 @@ -import { ApolloServer, type BaseContext } from "@apollo/server"; -import { fixitSchema } from "@/graphql/schema.js"; +import { ApolloServer, type ContextFunction, type BaseContext } from "@apollo/server"; +import { ApolloServerPluginDrainHttpServer } from "@apollo/server/plugin/drainHttpServer"; +import { getTypeSafeError } from "@nerdware/ts-type-safety-utils"; +import { GraphQLError } from "graphql"; +import { formatApolloError } from "@/graphql/GraphQLError/helpers.js"; +import { schema } from "@/graphql/schema.js"; import { ENV } from "@/server/env"; -import type { FixitApiAuthTokenPayload } from "@/utils/AuthToken.js"; +import { AuthService } from "@/services/AuthService"; +import { HTTP_ERROR_CONFIGS, type HttpError } from "@/utils/httpErrors.js"; +import type { AuthTokenPayload } from "@/types/open-api.js"; import type { Request } from "express"; +import type { Server as HttpServer } from "node:http"; -export const apolloServer = new ApolloServer({ - schema: fixitSchema, +export const apolloServer = new ApolloServer({ + schema, + formatError: formatApolloError, csrfPrevention: true, - introspection: ENV.NODE_ENV === "development", + introspection: ENV.IS_DEV, includeStacktraceInErrorResponses: !ENV.IS_PROD, - plugins: [ - ...(ENV.NODE_ENV === "development" - ? [(await import("@apollo/server/plugin/inlineTrace")).ApolloServerPluginInlineTrace()] - : [(await import("@apollo/server/plugin/disabled")).ApolloServerPluginLandingPageDisabled()]), - ], -}); + status400ForVariableCoercionErrors: true, +}) as ApolloServerWithContext; -// Run required init logic for integrating with Express -await apolloServer.start(); +/** + * `ApolloServer` with a custom `configurePlugins` method. + */ +export type ApolloServerWithContext = ApolloServer & { + /** When called, this function adds env-appropriate plugins to the ApolloServer instance. */ + configurePlugins: (params: { httpServer: HttpServer }) => Promise; +}; + +apolloServer.configurePlugins = async ({ httpServer }) => { + apolloServer.addPlugin(ApolloServerPluginDrainHttpServer({ httpServer })); + apolloServer.addPlugin( + ENV.IS_DEV + ? (await import("@apollo/server/plugin/inlineTrace")).ApolloServerPluginInlineTrace() + : (await import("@apollo/server/plugin/disabled")).ApolloServerPluginLandingPageDisabled() + ); +}; + +/////////////////////////////////////////////////////////////////////////////// +// ApolloServer Context + +/** + * The `context` object available to all ApolloServer resolvers and plugins. + */ +export type ApolloServerContext = BaseContext & { + req: ApolloServerContextRequestFields; + user: AuthTokenPayload; +}; + +/** + * ApolloServer `context.req` — contains properties from the Express `req` object. + */ +export type ApolloServerContextRequestFields = Pick< + Request, unknown, Record>, + "body" | "hostname" | "ip" | "ips" | "method" | "originalUrl" | "path" | "protocol" | "subdomains" +>; /** - * The execution context object available to all GQL resolvers. + * This function is an [ApolloServer `context` function][apollo-context-fn] + * which is provided to Apollo's [`expressMiddleware`][apollo-express-mw] — the + * entrypoint for the GraphQL API. + * + * The function validates and authenticates requests on the /api route, and ensures + * that no request reaches any GQL resolver unless the following are all true: + * + * 1. The request contains a valid unexpired AuthToken which was signed by the relevant key. + * 2. The AuthToken payload contains required User information. + * 3. The User's subscription is both active and unexpired. + * + * If all criteria are met, the User's information is attached to the GQL context object, + * along with useful properties from the request object. + * + * [apollo-express-mw]: https://www.apollographql.com/docs/apollo-server/api/express-middleware + * [apollo-context-fn]: https://www.apollographql.com/docs/apollo-server/data/context/#the-context-function */ -export interface ApolloServerResolverContext extends BaseContext, Partial { - /** The currently authenticated User's AuthToken payload. */ - user: FixitApiAuthTokenPayload; -} +export const getAuthenticatedApolloContext: ContextFunction< + [{ req: Request }], + ApolloServerContext +> = async ({ req }): Promise => { + try { + // Authenticate the user + const authenticatedUser = await AuthService.authenticateUser.viaAuthHeaderToken(req); + // Ensure the User is authorized to access paid content + AuthService.verifyUserIsAuthorized.toAccessPaidContent({ authenticatedUser }); + // Return the ApolloServer context object + return { + user: authenticatedUser, + req: { + body: req.body as Record, + hostname: req.hostname, + ip: req.ip, + ips: req.ips, + method: req.method, + originalUrl: req.originalUrl, + path: req.path, + protocol: req.protocol, + subdomains: req.subdomains, + }, + }; + } catch (err) { + const error = getTypeSafeError(err); + // Expected error.statusCode values: 401 or 403 (default to 401 if undefined for whatever reason) + const errorStatusCode = (error as Partial).statusCode ?? 401; + // Get the HTTP-error-config for the statusCode + const httpErrorConfig = HTTP_ERROR_CONFIGS[errorStatusCode] ?? HTTP_ERROR_CONFIGS[401]; + // Re-throw as GraphQLError (ApolloServer's formatError fn is not called for ctx-fn errors) + throw new GraphQLError(httpErrorConfig.defaultErrorMsg, { + extensions: httpErrorConfig.gqlErrorExtensions, + }); + } +}; diff --git a/src/controllers/AdminController/cspViolation.ts b/src/controllers/AdminController/cspViolation.ts new file mode 100644 index 00000000..0b04c977 --- /dev/null +++ b/src/controllers/AdminController/cspViolation.ts @@ -0,0 +1,33 @@ +import { sanitizeJsonString } from "@nerdware/ts-string-helpers"; +import { safeJsonStringify } from "@nerdware/ts-type-safety-utils"; +import { logger } from "@/utils/logger.js"; +import type { ApiRequestHandler } from "@/controllers/ApiController.js"; + +/** + * This controller handles CSP-violation-report requests. + * + * > Endpoint: `POST /api/admin/csp-violation` + * + * > **For the structure of the CSP-report object, see [MDN Sample CSP Violation Report][mdn-sample-report].** + * + * [mdn-sample-report]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only#sample_violation_report + */ +export const cspViolation: ApiRequestHandler<"/admin/csp-violation"> = (req, res, next) => { + try { + // Sanitize the CSP-report json: + const sanitizedJsonStr = sanitizeJsonString(safeJsonStringify(req.body)); + + // Parse the sanitized JSON string: + try { + const report = JSON.parse(sanitizedJsonStr); + logger.security(report, "CSP VIOLATION REPORT"); + } catch { + logger.security(sanitizedJsonStr, "CSP VIOLATION REPORT (RECEIVED INVALID JSON)"); + } + + // Send 204 (No Content) response: + res.status(204).end(); + } catch (err) { + next(err); + } +}; diff --git a/src/controllers/AdminController/healthcheck.ts b/src/controllers/AdminController/healthcheck.ts new file mode 100644 index 00000000..71f3ee8f --- /dev/null +++ b/src/controllers/AdminController/healthcheck.ts @@ -0,0 +1,14 @@ +import type { ApiRequestHandler } from "@/controllers/ApiController.js"; + +/** + * This controller responds to healthcheck requests. + * + * > Endpoint: `GET /api/admin/healthcheck` + */ +export const healthcheck: ApiRequestHandler<"/admin/healthcheck"> = (req, res, next) => { + try { + res.status(200).json({ message: "SUCCESS" }); + } catch (err) { + next(err); + } +}; diff --git a/src/controllers/AdminController/index.ts b/src/controllers/AdminController/index.ts new file mode 100644 index 00000000..1e77eccb --- /dev/null +++ b/src/controllers/AdminController/index.ts @@ -0,0 +1,10 @@ +import { cspViolation } from "./cspViolation.js"; +import { healthcheck } from "./healthcheck.js"; + +/** + * This object contains request/response handlers for `/api/admin/*` routes. + */ +export const AdminController = { + cspViolation, + healthcheck, +} as const; diff --git a/src/controllers/ApiController.ts b/src/controllers/ApiController.ts new file mode 100644 index 00000000..52e7e02e --- /dev/null +++ b/src/controllers/ApiController.ts @@ -0,0 +1,120 @@ +import { getTypeSafeError } from "@nerdware/ts-type-safety-utils"; +import { UserInputError } from "@/utils/httpErrors.js"; +import type { + Paths, + RestApiEndpoint, + RestApiGETendpoint, + RestApiPOSTendpoint, + RestApiRequestBodyByPath, + RestApiResponseByPath, +} from "@/types/open-api.js"; +import type { ZodObjectWithShape } from "@/types/zod.js"; +import type { RequestHandler } from "express"; +import type { SetReturnType } from "type-fest"; +import type { ZodEffects, ZodRecord, ZodOptional } from "zod"; + +/** + * An API `RequestHandler` with `req` and `res` typed to match the {@link RestApiEndpoint} param. + */ +export type ApiRequestHandler = SetReturnType< + RequestHandler< + Paths[Path]["parameters"]["path"] extends object + ? Paths[Path]["parameters"]["path"] + : Record, + RestApiResponseByPath[Path], + Path extends keyof RestApiRequestBodyByPath ? RestApiRequestBodyByPath[Path] : never, + Paths[Path]["parameters"]["query"] extends object + ? Paths[Path]["parameters"]["query"] + : Record, + Record + >, + void | Promise +>; + +/** + * The `reqBodyZodSchema` param type for the {@link apiController} function. + */ +type ReqBodyZodSchemaParam< + Path extends RestApiPOSTendpoint, + Schema extends ZodObjectOptionalOnUndefined< + RestApiRequestBodyByPath[Path] + > = ZodObjectOptionalOnUndefined, +> = Schema | ZodEffects | ZodRecord; + +/** + * Adds `ZodOptional` to `req.body` typings when `req.body` is optional. + */ +type ZodObjectOptionalOnUndefined | undefined> = + undefined extends ReqBody + ? ZodOptional>> + : ZodObjectWithShape>; + +/** + * Parameters of the {@link apiController} function. + */ +type ApiControllerParams = Path extends RestApiPOSTendpoint + ? [ReqBodyZodSchemaParam, ApiRequestHandler] + : [ApiRequestHandler]; + +/** + * Call signature overloads for the {@link apiController} function. + */ +type ApiController = { + // GET endpoint signature: + (controller: ApiRequestHandler): ApiRequestHandler; + // POST endpoint signature: + ( + reqBodyZodSchema: ReqBodyZodSchemaParam, + controller: ApiRequestHandler + ): ApiRequestHandler; +}; + +/** + * This function creates an API controller with error handling and request body validation. + */ +export const ApiController: ApiController = ( + ...args: ApiControllerParams +): ApiRequestHandler => { + let controller: ApiRequestHandler; + + if (args.length === 1) { + // GET endpoint: + controller = args[0] as unknown as ApiRequestHandler; + } else { + // POST endpoint: + + // Get the Zod schema for the request body: + const zodReqBodySchema = args[0]; + const controllerArg = args[1] as SetReturnType>; + + // Wrap controllerArg with req.body sanitization+validation logic: + controller = async (req, res, next) => { + try { + const validatedReqBody = zodReqBodySchema.parse(req.body); + + req.body = validatedReqBody as Path extends keyof RestApiRequestBodyByPath + ? RestApiRequestBodyByPath[Path] + : never; + + await controllerArg(req, res, next); + } catch (err) { + next( + new UserInputError( + getTypeSafeError(err, { + fallBackErrMsg: "One or more of the provided values are invalid", + }).message + ) + ); + } + }; + } + + // Return the controller wrapped in a try-catch block for error handling: + return async (req, res, next) => { + try { + await controller(req, res, next); + } catch (err) { + next(err); + } + }; +}; diff --git a/src/controllers/AuthController/googleTokenLogin.ts b/src/controllers/AuthController/googleTokenLogin.ts new file mode 100644 index 00000000..d5bacf29 --- /dev/null +++ b/src/controllers/AuthController/googleTokenLogin.ts @@ -0,0 +1,51 @@ +import { sanitizeJWT, isValidJWT } from "@nerdware/ts-string-helpers"; +import { z as zod } from "zod"; +import { ApiController } from "@/controllers/ApiController.js"; +import { AuthService } from "@/services/AuthService"; + +/** + * This controller method logs in a user using a Google ID Token. + * This endpoint is used for Google OAuth2 OneTap login. + * + * > Endpoint: `POST /api/auth/google-token` + */ +export const googleTokenLogin = ApiController<"/auth/google-token">( + // Req body schema: + zod + .object({ + googleIDToken: zod.string().transform(sanitizeJWT).refine(isValidJWT, { + message: "Invalid Google ID Token", + }), + }) + .strict(), + // Controller logic: + async (req, res) => { + const { googleIDToken } = req.body; + + // Parse the Google ID Token: + const { email, googleID } = await AuthService.parseGoogleOAuth2IDToken(googleIDToken); + + // Authenticate the user: + const authenticatedUser = await AuthService.authenticateUser.viaLoginCredentials({ + email, + googleID, + }); + + // Pre-fetch User items: + const { userItems, userSubscription, userStripeConnectAccount } = + await AuthService.preFetchAndSyncUserItems({ authenticatedUserID: authenticatedUser.id }); + + // Create a new AuthToken for the user: + const authToken = AuthService.createAuthToken({ + ...authenticatedUser, + subscription: userSubscription, + stripeConnectAccount: userStripeConnectAccount, + }); + + // Send response: + res.status(200).json({ + token: authToken.toString(), + userItems, + }); + } +); diff --git a/src/controllers/AuthController/index.ts b/src/controllers/AuthController/index.ts new file mode 100644 index 00000000..bd0c0281 --- /dev/null +++ b/src/controllers/AuthController/index.ts @@ -0,0 +1,18 @@ +import { googleTokenLogin } from "./googleTokenLogin.js"; +import { login } from "./login.js"; +import { passwordReset } from "./passwordReset.js"; +import { pwResetInit } from "./passwordResetInit.js"; +import { refreshAuthToken } from "./refreshAuthToken.js"; +import { registerNewUser } from "./registerNewUser.js"; + +/** + * This object contains request/response handlers for `/api/auth/*` routes. + */ +export const AuthController = { + googleTokenLogin, + login, + pwResetInit, + passwordReset, + refreshAuthToken, + registerNewUser, +} as const; diff --git a/src/controllers/AuthController/login.ts b/src/controllers/AuthController/login.ts new file mode 100644 index 00000000..92bfdb43 --- /dev/null +++ b/src/controllers/AuthController/login.ts @@ -0,0 +1,88 @@ +import { + sanitizeEmail, + isValidEmail, + sanitizePassword, + isValidPassword, + sanitizeJWT, + isValidJWT, +} from "@nerdware/ts-string-helpers"; +import { hasKey } from "@nerdware/ts-type-safety-utils"; +import { z as zod } from "zod"; +import { ApiController } from "@/controllers/ApiController.js"; +import { AuthService } from "@/services/AuthService"; +import type { ParsedGoogleOAuth2IDTokenFields } from "@/services/AuthService/GoogleOAuth2IDToken.js"; + +/** + * The Zod schema for the request body of the `/auth/login` endpoint. + */ +export const loginReqBodyZodSchema = zod + .object({ + email: zod.string().toLowerCase().transform(sanitizeEmail).refine(isValidEmail), + password: zod + .string() + .optional() + .transform((value) => (value ? sanitizePassword(value) : value)) + .refine((value) => (value ? isValidPassword(value) : value === undefined)), + googleIDToken: zod + .string() + .optional() + .transform((value) => (value ? sanitizeJWT(value) : value)) + .refine((value) => (value ? isValidJWT(value) : value === undefined)), + expoPushToken: zod + .string() + .optional() + .transform((value) => (value ? sanitizeJWT(value) : value)) + .refine((value) => (value ? isValidJWT(value) : value === undefined)), + }) + .strict() + .refine( + // Require either a `password` or `googleIDToken`: + (reqBody) => hasKey(reqBody, "password") || hasKey(reqBody, "googleIDToken"), + { message: "Invalid login credentials" } + ); + +/** + * This controller method logs in a user. + * + * > Endpoint: `POST /api/auth/login` + */ +export const login = ApiController<"/auth/login">( + // Req body schema: + loginReqBodyZodSchema, + // Controller logic: + async (req, res) => { + const { email, password, googleIDToken, expoPushToken } = req.body; + + // If a `googleIDToken` is provided, parse it: + const { googleID }: Partial = googleIDToken + ? await AuthService.parseGoogleOAuth2IDToken(googleIDToken) + : {}; + + // Authenticate the user: + const authenticatedUser = await AuthService.authenticateUser.viaLoginCredentials({ + email, + password, + googleID, + }); + + // Pre-fetch User items: + const { userItems, userSubscription, userStripeConnectAccount } = + await AuthService.preFetchAndSyncUserItems({ + authenticatedUserID: authenticatedUser.id, + expoPushToken, + }); + + // Create a new AuthToken for the user: + const authToken = AuthService.createAuthToken({ + ...authenticatedUser, + subscription: userSubscription, + stripeConnectAccount: userStripeConnectAccount, + }); + + // Send response: + res.status(200).json({ + token: authToken.toString(), + userItems, + }); + } +); diff --git a/src/controllers/AuthController/passwordReset.ts b/src/controllers/AuthController/passwordReset.ts new file mode 100644 index 00000000..b6391722 --- /dev/null +++ b/src/controllers/AuthController/passwordReset.ts @@ -0,0 +1,31 @@ +import { sanitizePassword, isValidPassword, sanitizeHex } from "@nerdware/ts-string-helpers"; +import { z as zod } from "zod"; +import { ApiController } from "@/controllers/ApiController.js"; +import { PasswordResetToken } from "@/models/PasswordResetToken"; +import { AuthService } from "@/services/AuthService"; + +/** + * This controller method completes the password reset process for a user. + * + * > Endpoint: `POST /api/auth/password-reset` + */ +export const passwordReset = ApiController<"/auth/password-reset">( + // Req body schema: + zod + .object({ + password: zod.string().transform(sanitizePassword).refine(isValidPassword), + passwordResetToken: zod + .string() + .transform(sanitizeHex) + .refine(PasswordResetToken.isRawTokenProperlyEncoded), + }) + .strict(), + // Controller logic: + async (req, res) => { + const { password, passwordResetToken } = req.body; + + await AuthService.resetPassword({ password, passwordResetToken }); + + res.sendStatus(200); + } +); diff --git a/src/controllers/AuthController/passwordResetInit.ts b/src/controllers/AuthController/passwordResetInit.ts new file mode 100644 index 00000000..6ae7af6d --- /dev/null +++ b/src/controllers/AuthController/passwordResetInit.ts @@ -0,0 +1,24 @@ +import { sanitizeEmail, isValidEmail } from "@nerdware/ts-string-helpers"; +import { z as zod } from "zod"; +import { ApiController } from "@/controllers/ApiController.js"; +import { AuthService } from "@/services/AuthService"; + +/** + * This controller method begins the password reset process for a user. + * + * > Endpoint: `POST /api/auth/password-reset-init` + */ +export const pwResetInit = ApiController<"/auth/password-reset-init">( + zod + .object({ + email: zod.string().toLowerCase().transform(sanitizeEmail).refine(isValidEmail), + }) + .strict(), + async (req, res) => { + const { email } = req.body; + + await AuthService.sendPasswordResetEmail({ email }); + + res.sendStatus(200); + } +); diff --git a/src/controllers/AuthController/refreshAuthToken.ts b/src/controllers/AuthController/refreshAuthToken.ts new file mode 100644 index 00000000..cccbb23a --- /dev/null +++ b/src/controllers/AuthController/refreshAuthToken.ts @@ -0,0 +1,48 @@ +import { sanitizeJWT, isValidJWT } from "@nerdware/ts-string-helpers"; +import { z as zod } from "zod"; +import { ApiController } from "@/controllers/ApiController.js"; +import { AuthService } from "@/services/AuthService"; + +/** + * This controller refreshes a user's AuthToken (if valid). + * + * > Endpoint: `POST /api/auth/token` + */ +export const refreshAuthToken = ApiController<"/auth/token">( + // Req body schema: + zod + .object({ + expoPushToken: zod + .string() + .optional() + .transform((value) => (value ? sanitizeJWT(value) : value)) + .refine((value) => (value ? isValidJWT(value) : value === undefined)), + }) + .strict() + .optional(), + // Controller logic: + async (req, res) => { + // Validate and decode the AuthToken from the 'Authorization' header: + const authenticatedUser = await AuthService.authenticateUser.viaAuthHeaderToken(req); + + // Pre-fetch User items: + const { userItems, userSubscription, userStripeConnectAccount } = + await AuthService.preFetchAndSyncUserItems({ + authenticatedUserID: authenticatedUser.id, + expoPushToken: req.body?.expoPushToken, + }); + + // Create a new AuthToken for the user: + const newAuthToken = AuthService.createAuthToken({ + ...authenticatedUser, + subscription: userSubscription, + stripeConnectAccount: userStripeConnectAccount, + }); + + // Send response: + res.status(200).json({ + token: newAuthToken.toString(), + userItems, + }); + } +); diff --git a/src/controllers/AuthController/registerNewUser.ts b/src/controllers/AuthController/registerNewUser.ts new file mode 100644 index 00000000..0a14d80f --- /dev/null +++ b/src/controllers/AuthController/registerNewUser.ts @@ -0,0 +1,69 @@ +import { + sanitizeHandle, + isValidHandle, + sanitizePhone, + isValidPhone, +} from "@nerdware/ts-string-helpers"; +import { hasKey } from "@nerdware/ts-type-safety-utils"; +import { z as zod } from "zod"; +import { ApiController } from "@/controllers/ApiController.js"; +import { AuthService } from "@/services/AuthService"; +import { UserService } from "@/services/UserService"; +import { loginReqBodyZodSchema } from "./login.js"; +import type { ParsedGoogleOAuth2IDTokenFields } from "@/services/AuthService/GoogleOAuth2IDToken.js"; + +/** + * The Zod schema for the request body of the `/auth/register` endpoint. + */ +export const registerNewUserReqBodyZodSchema = loginReqBodyZodSchema + .innerType() + .extend({ + handle: zod.string().transform(sanitizeHandle).refine(isValidHandle), + phone: zod + .string() + .nullish() + .default(null) + .transform((value) => (value ? sanitizePhone(value) : null)) + .refine((value) => (value ? isValidPhone(value) : value === null)), + }) + .refine( + // Require either a `password` or `googleIDToken`: + (reqBody) => hasKey(reqBody, "password") || hasKey(reqBody, "googleIDToken"), + { message: "Invalid login credentials" } + ); + +/** + * This controller method registers a new user. + * + * > Endpoint: `POST /api/auth/register` + */ +export const registerNewUser = ApiController<"/auth/register">( + // Req body schema: + registerNewUserReqBodyZodSchema, + // Controller logic: + async (req, res) => { + const { handle, email, phone = null, password, googleIDToken, expoPushToken } = req.body; + + // If a `googleIDToken` is provided, parse it: + const { googleID, profile }: Partial = googleIDToken + ? await AuthService.parseGoogleOAuth2IDToken(googleIDToken) + : {}; + + // Register the new User: + const newUser = await UserService.registerNewUser({ + handle, + email, + phone, + password, + googleID, + profile, + expoPushToken, + }); + + // Create a new AuthToken for the user: + const authToken = AuthService.createAuthToken(newUser); + + // Send response: + res.status(201).json({ token: authToken.toString() }); + } +); diff --git a/src/controllers/ConnectController/createAccountLink.ts b/src/controllers/ConnectController/createAccountLink.ts new file mode 100644 index 00000000..b038dc47 --- /dev/null +++ b/src/controllers/ConnectController/createAccountLink.ts @@ -0,0 +1,41 @@ +import { sanitizeURL, isValidURL } from "@nerdware/ts-string-helpers"; +import { z as zod } from "zod"; +import { ApiController } from "@/controllers/ApiController.js"; +import { AccountService } from "@/services/AccountService"; +import { AuthService } from "@/services/AuthService"; + +/** + * This schema defines a `req.body` object with a required `returnURL` string property. + */ +export const returnUrlReqBodySchema = zod + .object({ + returnURL: zod.string().transform(sanitizeURL).refine(isValidURL), + }) + .strict(); + +/** + * This controller returns a Stripe ConnectAccount link for an authenticated user. + * + * > Endpoint: `POST /api/connect/account-link` + */ +export const createAccountLink = ApiController<"/connect/account-link">( + // Req body schema: + returnUrlReqBodySchema, + // Controller logic: + async (req, res) => { + // Validate and decode the AuthToken from the 'Authorization' header: + const authenticatedUser = await AuthService.authenticateUser.viaAuthHeaderToken(req); + + // Get the provided `returnURL`: + const { returnURL } = req.body; + + // Get the Stripe AccountLink: + const { url: stripeLink } = await AccountService.createStripeConnectAccountLink({ + authenticatedUser, + returnURL, + }); + + // Send response: + res.status(201).json({ stripeLink }); + } +); diff --git a/src/controllers/ConnectController/createDashboardLink.ts b/src/controllers/ConnectController/createDashboardLink.ts new file mode 100644 index 00000000..ca152e7b --- /dev/null +++ b/src/controllers/ConnectController/createDashboardLink.ts @@ -0,0 +1,19 @@ +import { ApiController } from "@/controllers/ApiController.js"; +import { AccountService } from "@/services/AccountService"; +import { AuthService } from "@/services/AuthService"; + +/** + * This controller returns a Stripe customer dashboard link for an authenticated user. + * + * > Endpoint: `GET /api/connect/dashboard-link` + */ +export const createDashboardLink = ApiController<"/connect/dashboard-link">(async (req, res) => { + // Validate and decode the AuthToken from the request header: + const authenticatedUser = await AuthService.authenticateUser.viaAuthHeaderToken(req); + + // Get the Stripe customer dashboard link: + const { url: stripeLink } = await AccountService.createDashboardLink({ authenticatedUser }); + + // Send response: + res.status(201).json({ stripeLink }); +}); diff --git a/src/controllers/ConnectController/index.ts b/src/controllers/ConnectController/index.ts new file mode 100644 index 00000000..68d1a533 --- /dev/null +++ b/src/controllers/ConnectController/index.ts @@ -0,0 +1,10 @@ +import { createAccountLink } from "./createAccountLink.js"; +import { createDashboardLink } from "./createDashboardLink.js"; + +/** + * This object contains request/response handlers for `/api/connect/*` routes. + */ +export const ConnectController = { + createAccountLink, + createDashboardLink, +} as const; diff --git a/src/controllers/README.md b/src/controllers/README.md new file mode 100644 index 00000000..aefb1e3b --- /dev/null +++ b/src/controllers/README.md @@ -0,0 +1,11 @@ +# Controllers + +This directory contains the application's `controllers`. + +### Controller Design Goals + +Each `controller` is designed to achieve the following design goals: + +- Serve as the primary location for client request/response handling logic. +- Sanitize and validate incoming requests and any raw input values provided by clients. +- Invoke [service](../services/README.md) and [model](../models/README.md) methods as necessary to fulfill client requests. diff --git a/src/controllers/SubscriptionsController/checkPromoCode.ts b/src/controllers/SubscriptionsController/checkPromoCode.ts new file mode 100644 index 00000000..fd5a7d3b --- /dev/null +++ b/src/controllers/SubscriptionsController/checkPromoCode.ts @@ -0,0 +1,34 @@ +import { sanitizeAlphabetic, isValidAlphabetic } from "@nerdware/ts-string-helpers"; +import { z as zod } from "zod"; +import { ApiController } from "@/controllers/ApiController.js"; +import { AuthService } from "@/services/AuthService"; +import { CheckoutService } from "@/services/CheckoutService"; + +/** + * This controller returns a Stripe Customer Portal link, which allows the User to + * manage their subscription and payment methods. + * + * > Endpoint: `POST /api/subscriptions/check-promo-code` + */ +export const checkPromoCode = ApiController<"/subscriptions/check-promo-code">( + // Req body schema: + zod + .object({ + promoCode: zod.string().transform(sanitizeAlphabetic).refine(isValidAlphabetic), + }) + .strict(), + // Controller logic: + async (req, res) => { + // Validate the request (don't need to use the decoded payload, just validate the token) + await AuthService.authenticateUser.viaAuthHeaderToken(req); + + // Get the provided `promoCode`: + const { promoCode } = req.body; + + // Get the promo code info: + const promoCodeInfo = CheckoutService.checkPromoCode({ promoCode }); + + // Send response: + res.status(200).json({ promoCodeInfo }); + } +); diff --git a/src/controllers/SubscriptionsController/createCustomerBillingPortalLink.ts b/src/controllers/SubscriptionsController/createCustomerBillingPortalLink.ts new file mode 100644 index 00000000..841deb3c --- /dev/null +++ b/src/controllers/SubscriptionsController/createCustomerBillingPortalLink.ts @@ -0,0 +1,32 @@ +import { ApiController } from "@/controllers/ApiController.js"; +import { returnUrlReqBodySchema } from "@/controllers/ConnectController/createAccountLink"; +import { AccountService } from "@/services/AccountService"; +import { AuthService } from "@/services/AuthService"; + +/** + * This controller returns a Stripe Customer Portal link, which allows the User to + * manage their subscription and payment methods. + * + * > Endpoint: `POST /api/subscriptions/customer-portal` + */ +export const createCustomerBillingPortalLink = ApiController<"/subscriptions/customer-portal">( + // Req body schema: + returnUrlReqBodySchema, + // Controller logic: + async (req, res) => { + // Validate and decode the AuthToken from the 'Authorization' header: + const authenticatedUser = await AuthService.authenticateUser.viaAuthHeaderToken(req); + + // Get the provided `returnURL`: + const { returnURL } = req.body; + + // Get the Stripe link: + const { url: stripeLink } = await AccountService.createCustomerBillingPortalLink({ + authenticatedUser, + returnURL, + }); + + // Send response: + res.status(201).json({ stripeLink }); + } +); diff --git a/src/controllers/SubscriptionsController/index.ts b/src/controllers/SubscriptionsController/index.ts new file mode 100644 index 00000000..44ba3598 --- /dev/null +++ b/src/controllers/SubscriptionsController/index.ts @@ -0,0 +1,12 @@ +import { checkPromoCode } from "./checkPromoCode.js"; +import { createCustomerBillingPortalLink } from "./createCustomerBillingPortalLink.js"; +import { submitPayment } from "./submitPayment.js"; + +/** + * This object contains request/response handlers for `/api/subscriptions/*` routes. + */ +export const SubscriptionsController = { + checkPromoCode, + createCustomerBillingPortalLink, + submitPayment, +} as const; diff --git a/src/controllers/SubscriptionsController/submitPayment.ts b/src/controllers/SubscriptionsController/submitPayment.ts new file mode 100644 index 00000000..c5ddf82e --- /dev/null +++ b/src/controllers/SubscriptionsController/submitPayment.ts @@ -0,0 +1,64 @@ +import { sanitizeAlphabetic } from "@nerdware/ts-string-helpers"; +import { z as zod } from "zod"; +import { ApiController } from "@/controllers/ApiController.js"; +import { pricesCache } from "@/lib/cache/pricesCache.js"; +import { promoCodesCache } from "@/lib/cache/promoCodesCache.js"; +import { isValidStripeID, sanitizeStripeID } from "@/lib/stripe/helpers.js"; +import { SUBSCRIPTION_ENUMS } from "@/models/UserSubscription/enumConstants.js"; +import { AuthService } from "@/services/AuthService"; +import { CheckoutService } from "@/services/CheckoutService"; + +/** + * This controller returns + * + * > Endpoint: `POST /api/subscriptions/submit-payment` + */ +export const submitPayment = ApiController<"/subscriptions/submit-payment">( + // Req body schema: + zod + .object({ + selectedSubscription: zod + .enum(SUBSCRIPTION_ENUMS.PRICE_NAMES) + .transform(sanitizeAlphabetic) + .refine(pricesCache.has), + paymentMethodID: zod + .string() + .transform(sanitizeStripeID) + .refine(isValidStripeID.paymentMethod), + promoCode: zod + .string() + .optional() + .transform((value) => (value ? sanitizeAlphabetic(value) : value)) + .refine((value) => (value ? promoCodesCache.has(value) : value === undefined)), + }) + .strict(), + // Controller logic: + async (req, res) => { + // Validate and decode the AuthToken from the 'Authorization' header: + const authenticatedUser = await AuthService.authenticateUser.viaAuthHeaderToken(req); + + // Get the provided args: + const { paymentMethodID, selectedSubscription, promoCode } = req.body; + + // Get the Stripe link: + const { checkoutCompletionInfo, subscription } = await CheckoutService.processPayment({ + user: authenticatedUser, + selectedSubscription, + paymentMethodID, + promoCode, + request: { + ip: req.ip!, + userAgent: req.get("User-Agent")!, + }, + }); + + // Update the user's AuthToken with new subscription info: + const newAuthToken = AuthService.createAuthToken({ ...authenticatedUser, subscription }); + + // Send response: + res.status(201).json({ + checkoutCompletionInfo, + token: newAuthToken.toString(), + }); + } +); diff --git a/src/controllers/WebhooksController/StripeWebhooksController/README.md b/src/controllers/WebhooksController/StripeWebhooksController/README.md new file mode 100644 index 00000000..b80c8d66 --- /dev/null +++ b/src/controllers/WebhooksController/StripeWebhooksController/README.md @@ -0,0 +1,54 @@ +# Stripe Webhooks Controller + +This controller maps Stripe webhook event-names to their respective event-handlers. + +### Event-Handler Design Goals + +Each `eventHandler` is designed to achieve the following design goals: + +- Each event handler takes the event's `data.object` as its sole argument. +- Each event handler is responsible for logging any errors it encounters. +- Events with function-handlers are considered _actionable_ events (see below). +- Events with `null`-handlers are considered _non-actionable_ events (see below). +- Events not included are considered _unhandled_ events (see below). + +## Stripe Webhook Event Actionability + +This API places each event-type into 1 of 3 categories: + +1. ### _`actionable`_`events` + + - ✅ Have been registered with Stripe + - ✅ Have one or more associated event-handlers + +2. ### _`non-actionable`_`events` + + - ✅ Have been registered with Stripe + - ❌ Do NOT have any associated event-handlers, typically because they contain + data that is not persisted by the Fixit back-end at this time. + +3. ### _`unhandled`_`events` + + - ❌ Have NOT been registered with Stripe, and therefore are NOT expected to be received. + - ❌ Do NOT have any associated event-handlers + > _If an unhandled event is received, it is logged as an error._ + +## Customer Portal Events + +The events listed below may be triggered by actions taken by Customers in the Stripe-provided +customer portal. Descriptions of each event and the data contained in its `data.object` can be +found [here](https://stripe.com/docs/customer-management/integrate-customer-portal#webhooks). + +- `billing_portal.configuration.created` +- `billing_portal.configuration.updated` +- `billing_portal.session.created` +- `customer.subscription.deleted` +- `customer.subscription.paused` +- `customer.subscription.resumed` +- `customer.subscription.updated` +- `customer.tax_id.created` +- `customer.tax_id.deleted` +- `customer.tax_id.updated` +- `customer.updated` +- `payment_method.attached` +- `payment_method.detached` diff --git a/src/middleware/stripeWebhooks/connectAccountUpdated.ts b/src/controllers/WebhooksController/StripeWebhooksController/connectAccountUpdated.ts similarity index 86% rename from src/middleware/stripeWebhooks/connectAccountUpdated.ts rename to src/controllers/WebhooksController/StripeWebhooksController/connectAccountUpdated.ts index 79dd6a65..fd0352db 100644 --- a/src/middleware/stripeWebhooks/connectAccountUpdated.ts +++ b/src/controllers/WebhooksController/StripeWebhooksController/connectAccountUpdated.ts @@ -1,5 +1,5 @@ import { getTypeSafeError } from "@nerdware/ts-type-safety-utils"; -import { UserStripeConnectAccount } from "@/models/UserStripeConnectAccount/UserStripeConnectAccount.js"; +import { UserStripeConnectAccount, SCA_SK_PREFIX_STR } from "@/models/UserStripeConnectAccount"; import { logger } from "@/utils/logger.js"; import type Stripe from "stripe"; @@ -17,25 +17,24 @@ export const connectAccountUpdated = async (rawStripeConnectAccountObj: Stripe.A payouts_enabled: payoutsEnabled, } = rawStripeConnectAccountObj; - let userID; + let userID: string | undefined; try { // Get "userID" needed for the primary key const queryResult = await UserStripeConnectAccount.query({ where: { id: stripeConnectAccountID, - sk: { beginsWith: UserStripeConnectAccount.SK_PREFIX }, + sk: { beginsWith: SCA_SK_PREFIX_STR }, }, limit: 1, }); - const userID = queryResult?.[0]?.userID; + const userID = queryResult[0]?.userID; - if (!userID) { + if (!userID) throw new Error( `UserStripeConnectAccount not found for StripeConnectAccount ID: "${stripeConnectAccountID}"` ); - } // Now update the user's Stripe Connect Account item in the db await UserStripeConnectAccount.updateItem( diff --git a/src/middleware/stripeWebhooks/customerSubscriptionDeleted.ts b/src/controllers/WebhooksController/StripeWebhooksController/customerSubscriptionDeleted.ts similarity index 83% rename from src/middleware/stripeWebhooks/customerSubscriptionDeleted.ts rename to src/controllers/WebhooksController/StripeWebhooksController/customerSubscriptionDeleted.ts index 1f590054..6174f198 100644 --- a/src/middleware/stripeWebhooks/customerSubscriptionDeleted.ts +++ b/src/controllers/WebhooksController/StripeWebhooksController/customerSubscriptionDeleted.ts @@ -1,5 +1,6 @@ import { getTypeSafeError } from "@nerdware/ts-type-safety-utils"; -import { UserSubscription } from "@/models/UserSubscription/UserSubscription.js"; +import { UserSubscription } from "@/models/UserSubscription"; +import { UserSubscriptionService } from "@/services/UserSubscriptionService"; import { logger } from "@/utils/logger.js"; import type Stripe from "stripe"; @@ -12,9 +13,10 @@ export const customerSubscriptionDeleted = async ( rawStripeSubscriptionObj: Stripe.Subscription ) => { // Normalize the Stripe-provided fields first - const { id: subID, createdAt } = UserSubscription.normalizeStripeFields(rawStripeSubscriptionObj); + const { id: subID, createdAt } = + UserSubscriptionService.normalizeStripeFields(rawStripeSubscriptionObj); - let userID; + let userID: string | undefined; try { // Submit query for the UserSubscription item diff --git a/src/middleware/stripeWebhooks/customerSubscriptionUpdated.ts b/src/controllers/WebhooksController/StripeWebhooksController/customerSubscriptionUpdated.ts similarity index 88% rename from src/middleware/stripeWebhooks/customerSubscriptionUpdated.ts rename to src/controllers/WebhooksController/StripeWebhooksController/customerSubscriptionUpdated.ts index 8af65939..0a27c412 100644 --- a/src/middleware/stripeWebhooks/customerSubscriptionUpdated.ts +++ b/src/controllers/WebhooksController/StripeWebhooksController/customerSubscriptionUpdated.ts @@ -1,5 +1,6 @@ import { getTypeSafeError } from "@nerdware/ts-type-safety-utils"; -import { UserSubscription } from "@/models/UserSubscription/UserSubscription.js"; +import { UserSubscription } from "@/models/UserSubscription"; +import { UserSubscriptionService } from "@/services/UserSubscriptionService"; import { logger } from "@/utils/logger.js"; import type Stripe from "stripe"; @@ -24,9 +25,9 @@ export const customerSubscriptionUpdated = async ( priceID, status, createdAt, - } = UserSubscription.normalizeStripeFields(rawStripeSubscriptionObj); + } = UserSubscriptionService.normalizeStripeFields(rawStripeSubscriptionObj); - let userID; + let userID: string | undefined; try { // Submit query for the UserSubscription item diff --git a/src/controllers/WebhooksController/StripeWebhooksController/index.ts b/src/controllers/WebhooksController/StripeWebhooksController/index.ts new file mode 100644 index 00000000..b2ddda3d --- /dev/null +++ b/src/controllers/WebhooksController/StripeWebhooksController/index.ts @@ -0,0 +1,47 @@ +import { connectAccountUpdated } from "./connectAccountUpdated.js"; +import { customerSubscriptionDeleted } from "./customerSubscriptionDeleted.js"; +import { customerSubscriptionUpdated } from "./customerSubscriptionUpdated.js"; +import type Stripe from "stripe"; +import type { StripeWebhookHandler } from "../types.js"; + +/** + * This controller maps Stripe webhook event-names to their respective event-handlers. + * + * See the [StripeWebhooksController README](./README.md) for more info. + */ +export const StripeWebhooksController: { + readonly [EventName in Stripe.DiscriminatedEvent.Type]?: StripeWebhookHandler | null; +} = { + "account.application.authorized": null, + "account.application.deauthorized": null, + "account.external_account.created": null, + "account.external_account.deleted": null, + "account.external_account.updated": null, + "account.updated": connectAccountUpdated, + "billing_portal.configuration.created": null, + "billing_portal.configuration.updated": null, + "billing_portal.session.created": null, + "customer.subscription.created": customerSubscriptionUpdated, + "customer.subscription.deleted": customerSubscriptionDeleted, + "customer.subscription.paused": customerSubscriptionUpdated, + "customer.subscription.pending_update_applied": customerSubscriptionUpdated, + "customer.subscription.pending_update_expired": customerSubscriptionUpdated, + "customer.subscription.resumed": customerSubscriptionUpdated, + "customer.subscription.trial_will_end": customerSubscriptionUpdated, + "customer.subscription.updated": customerSubscriptionUpdated, + "customer.created": null, + "customer.deleted": null, + "customer.updated": null, + "customer.discount.created": null, + "customer.discount.deleted": null, + "customer.discount.updated": null, + "customer.source.created": null, + "customer.source.deleted": null, + "customer.source.expiring": null, + "customer.source.updated": null, + "customer.tax_id.created": null, + "customer.tax_id.deleted": null, + "customer.tax_id.updated": null, + "payment_method.attached": null, + "payment_method.detached": null, +}; diff --git a/src/controllers/WebhooksController/handleStripeWebhooks.ts b/src/controllers/WebhooksController/handleStripeWebhooks.ts new file mode 100644 index 00000000..04d64767 --- /dev/null +++ b/src/controllers/WebhooksController/handleStripeWebhooks.ts @@ -0,0 +1,69 @@ +import { getTypeSafeError, safeJsonStringify } from "@nerdware/ts-type-safety-utils"; +import express from "express"; +import { stripe } from "@/lib/stripe/stripeClient.js"; +import { ENV } from "@/server/env"; +import { logger } from "@/utils/logger.js"; +import { StripeWebhooksController } from "./StripeWebhooksController"; +import type { RequestHandler } from "express"; +import type Stripe from "stripe"; +import type { GetDataObjectTypeForEvent } from "./types.js"; + +/** + * `handleStripeWebhooks` is a tuple containing two Express middleware functions: + * + * 1. The first middleware function parses the incoming request body as raw JSON + * as required by the Stripe API. + * + * 2. The second middleware function constructs, validates, and logs all Stripe + * webhook events. If the event is valid, it then looks for a handler for the + * event's `type` (e.g., `"customer.subscription.updated"`). If the event has + * a handler defined on the {@link StripeWebhooksController}, it is called + * with the event's `data.object` as its sole argument. + * + * See the [StripeWebhooksController README](./StripeWebhooksController/README.md) + * for more info on how this API handles Stripe webhook events. + */ +export const handleStripeWebhooks: [RequestHandler, RequestHandler] = [ + express.raw({ type: "application/json" }), + async (req, res, next) => { + let event: Stripe.DiscriminatedEvent | undefined; + + try { + // Construct Stripe event object + event = stripe.webhooks.constructEvent( + req.body, + req.headers["stripe-signature"] as string | string[] | Buffer, + ENV.STRIPE.WEBHOOKS_SECRET + ) as Stripe.DiscriminatedEvent; + } catch (err: unknown) { + const error = getTypeSafeError(err); + logger.stripe(error, "Webhook signature verification failed"); + res.status(400).send(`Webhook Error: ${error.message}`); + } + + if (!event) return next(new Error("Stripe webook event object not found")); + + const eventHandler = StripeWebhooksController[event.type]; + + /* + If `eventHandler` is + truthy --> actionable event + null --> non-actionable event + undefined --> unhandled event + */ + + // Log the webhook and its actionability + logger.webhook( + `Stripe webhook received: "${event.type}" ` + + `(HANDLED: ${eventHandler !== undefined}, ACTIONABLE: ${!!eventHandler}) ` + + `EVENT DATA: ${safeJsonStringify(event.data.object, null, 2)}` + ); + + // If an event handler exists, invoke it and acknowledge receipt of the event + if (eventHandler) { + await eventHandler(event.data.object as GetDataObjectTypeForEvent); + } + + res.status(200).json({ received: true }); + }, +]; diff --git a/src/controllers/WebhooksController/index.ts b/src/controllers/WebhooksController/index.ts new file mode 100644 index 00000000..b444955f --- /dev/null +++ b/src/controllers/WebhooksController/index.ts @@ -0,0 +1,8 @@ +import { handleStripeWebhooks } from "./handleStripeWebhooks.js"; + +/** + * This object contains request/response handlers for `/api/webhooks/*` routes. + */ +export const WebhooksController = { + stripeWebhooks: handleStripeWebhooks, +} as const; diff --git a/src/controllers/WebhooksController/types.ts b/src/controllers/WebhooksController/types.ts new file mode 100644 index 00000000..4d8c8ef8 --- /dev/null +++ b/src/controllers/WebhooksController/types.ts @@ -0,0 +1,34 @@ +import type Stripe from "stripe"; +import type { ValueOf } from "type-fest"; + +/** + * Generic util which takes a Stripe event name and returns the event's `event.data.object` type. + * + * @example + * ```ts + * GetDataObjectTypeForEvent<"account.updated">; // Stripe.Account + * GetDataObjectTypeForEvent<"customer.subscription.updated">; // Stripe.Subscription + * GetDataObjectTypeForEvent<"account.external_account.created">; // Stripe.Card | Stripe.BankAccount + * ``` + * + * @see https://www.npmjs.com/package/stripe-event-types + */ +export type GetDataObjectTypeForEvent = ValueOf<{ + [Obj in Stripe.DiscriminatedEvent as { type: EventName } extends Pick + ? EventName + : never]: Obj extends { data: { object: object } } ? Obj["data"]["object"] : never; +}>; + +/** + * A map of every Stripe webhook event to its respective `event.data.object` type. + */ +export type StripeEventDataObjectMap = { + [EventType in Stripe.DiscriminatedEvent.Type]: GetDataObjectTypeForEvent; +}; + +/** + * A webhook handler for a specific Stripe event. @see {@link StripeEventDataObjectMap} + */ +export type StripeWebhookHandler = ( + dataObj: StripeEventDataObjectMap[EventName] +) => Promise; diff --git a/src/events/__mocks__/eventEmitter.ts b/src/events/__mocks__/eventEmitter.ts index 82dcbc45..c4c2f9ad 100644 --- a/src/events/__mocks__/eventEmitter.ts +++ b/src/events/__mocks__/eventEmitter.ts @@ -1,15 +1,15 @@ import { safeJsonStringify } from "@nerdware/ts-type-safety-utils"; import { logger } from "@/utils/logger.js"; +import type { BaseEventHandler } from "@/events/eventEmitter.js"; const { FixitEventEmitter } = await vi.importActual( "@/events/eventEmitter.js" ); export const eventEmitter = new FixitEventEmitter( - Object.fromEntries( - Object.keys(FixitEventEmitter.EVENT_HANDLERS).map((eventName) => [ - eventName, - [ + Object.keys(FixitEventEmitter.EVENT_HANDLERS).reduce( + (accum: Record>, eventName) => { + accum[eventName] = [ (...args: unknown[]) => { logger.test(`Event emitted: "${eventName}"`); if (args.length > 0) { @@ -17,8 +17,11 @@ export const eventEmitter = new FixitEventEmitter( logger.test(safeJsonStringify(args)); console.groupEnd(); // eslint-disable-line no-console } + return Promise.resolve(); }, - ], - ]) - ) as unknown as typeof FixitEventEmitter.EVENT_HANDLERS + ]; + return accum; + }, + {} + ) as typeof FixitEventEmitter.EVENT_HANDLERS ); diff --git a/src/events/eventEmitter.ts b/src/events/eventEmitter.ts index a6778ad3..7563a034 100644 --- a/src/events/eventEmitter.ts +++ b/src/events/eventEmitter.ts @@ -1,4 +1,5 @@ import { EventEmitter } from "events"; +import { sendConfirmationEmail } from "@/events/onCheckoutCompleted"; import { notifyAssigneeNewInvoice } from "@/events/onInvoiceCreated"; import { notifyAssigneeDeletedInvoice } from "@/events/onInvoiceDeleted"; import { notifyAssignorPaidInvoice } from "@/events/onInvoicePaid"; @@ -23,6 +24,7 @@ export class FixitEventEmitter implements Record<`emit${FixitEventName}`, (...args: any[]) => void> { static readonly EVENT_HANDLERS = { + CheckoutCompleted: [sendConfirmationEmail], InvoiceCreated: [notifyAssigneeNewInvoice], InvoiceUpdated: [notifyAssigneeUpdatedInvoice], InvoiceDeleted: [notifyAssigneeDeletedInvoice], @@ -32,12 +34,13 @@ export class FixitEventEmitter WorkOrderUpdated: [notifyAssigneeUpdatedWO], WorkOrderCancelled: [notifyAssigneeCancelledWO], WorkOrderCompleted: [notifyAssignorCompletedWO], - } as const; + } as const satisfies Record>; private getNamedEmitter(name: T): NamedEmitFn { return (...args) => this.emit(name, ...args); } + emitCheckoutCompleted = this.getNamedEmitter("CheckoutCompleted"); emitInvoiceCreated = this.getNamedEmitter("InvoiceCreated"); emitInvoiceUpdated = this.getNamedEmitter("InvoiceUpdated"); emitInvoiceDeleted = this.getNamedEmitter("InvoiceDeleted"); @@ -51,17 +54,17 @@ export class FixitEventEmitter constructor(eventHandlers = FixitEventEmitter.EVENT_HANDLERS) { super(); // Register each event's handler functions - Object.entries(eventHandlers).forEach(([eventName, eventHandlers]) => { - eventHandlers.forEach((eventHandler) => + for (const eventName in eventHandlers) { + eventHandlers[eventName as FixitEventName].forEach((handler: BaseEventHandler) => this.on( eventName, // Wrap each event handler in a try/catch block to prevent unhandled errors - async (...args: any[]) => { - await eventHandler(args[0], args[1]).catch((error) => logger.error(error)); + async (...args) => { + await handler(args[0], args[1]).catch((error: unknown) => logger.error(error)); } ) ); - }); + } } } @@ -75,8 +78,10 @@ export type FixitEventName = keyof FixitEventHandlers; export type NamedEmitFn = ( ...args: Parameters ) => void; +/** Event handler base type. */ +export type BaseEventHandler = (...args: any[]) => Promise; -// Augment EventEmitter to only allow the names of configured events to be emitted. +// This augments EventEmitter to only allow the names of configured events to be emitted. declare module "events" { interface EventEmitter { emit(eventName: FixitEventName, ...args: unknown[]): boolean; diff --git a/src/events/onCheckoutCompleted/index.ts b/src/events/onCheckoutCompleted/index.ts new file mode 100644 index 00000000..3b010039 --- /dev/null +++ b/src/events/onCheckoutCompleted/index.ts @@ -0,0 +1 @@ +export * from "./sendConfirmationEmail.js"; diff --git a/src/events/onCheckoutCompleted/sendConfirmationEmail.ts b/src/events/onCheckoutCompleted/sendConfirmationEmail.ts new file mode 100644 index 00000000..608634ae --- /dev/null +++ b/src/events/onCheckoutCompleted/sendConfirmationEmail.ts @@ -0,0 +1,49 @@ +import { isString } from "@nerdware/ts-type-safety-utils"; +import { pinpointClient } from "@/lib/pinpointClient"; +import { SUBSCRIPTION_PRODUCT_NAMES } from "@/models/UserSubscription/enumConstants.js"; +import { intToCurrencyStr } from "@/utils/formatters/currency.js"; +import { capitalize } from "@/utils/formatters/strings.js"; +import type { SubscriptionPriceName } from "@/types/graphql.js"; +import type { AuthTokenPayload } from "@/types/open-api.js"; + +/** + * Send confirmation email to User when `CheckoutCompleted` event is emitted. + * @event CheckoutCompleted + * @category events + */ +export const sendConfirmationEmail = async ({ + user, + priceName, + paymentIntentID, + amountPaid, +}: CheckoutConfirmationData) => { + if (!isString(amountPaid)) amountPaid = intToCurrencyStr(amountPaid); + + await pinpointClient.sendMessages({ + to: user.email, + ChannelType: "EMAIL", + TemplateConfiguration: { + EmailTemplate: { + Name: "checkout-confirmation-email", + }, + }, + MessageConfiguration: { + EmailMessage: { + Substitutions: { + productName: [ + `${SUBSCRIPTION_PRODUCT_NAMES.FIXIT_SUBSCRIPTION} (${capitalize(priceName)})`, + ], + paymentIntentID: [paymentIntentID ?? "N/A"], + amountPaid: [amountPaid], + }, + }, + }, + }); +}; + +export type CheckoutConfirmationData = { + user: AuthTokenPayload; + priceName: SubscriptionPriceName; + paymentIntentID: string | null | undefined; + amountPaid: string | number; +}; diff --git a/src/events/onInvoiceCreated/notifyAssigneeNewInvoice.test.ts b/src/events/onInvoiceCreated/notifyAssigneeNewInvoice.test.ts index 12cd3d87..d1f2391d 100644 --- a/src/events/onInvoiceCreated/notifyAssigneeNewInvoice.test.ts +++ b/src/events/onInvoiceCreated/notifyAssigneeNewInvoice.test.ts @@ -1,8 +1,8 @@ import { InvoicePushNotification } from "@/events/pushNotifications/InvoicePushNotification.js"; import { lambdaClient } from "@/lib/lambdaClient/lambdaClient.js"; -import { User, type UserItem } from "@/models/User/User.js"; +import { User, type UserItem } from "@/models/User"; import { notifyAssigneeNewInvoice } from "./notifyAssigneeNewInvoice.js"; -import type { InvoiceItem } from "@/models/Invoice/Invoice.js"; +import type { InvoiceItem } from "@/models/Invoice"; describe("notifyAssigneeNewInvoice", () => { test("sends a push notification to the assignee when the assignee has an expoPushToken", async () => { diff --git a/src/events/onInvoiceCreated/notifyAssigneeNewInvoice.ts b/src/events/onInvoiceCreated/notifyAssigneeNewInvoice.ts index d16b7ce2..c5c93509 100644 --- a/src/events/onInvoiceCreated/notifyAssigneeNewInvoice.ts +++ b/src/events/onInvoiceCreated/notifyAssigneeNewInvoice.ts @@ -1,7 +1,7 @@ import { InvoicePushNotification } from "@/events/pushNotifications/InvoicePushNotification.js"; import { lambdaClient } from "@/lib/lambdaClient/lambdaClient.js"; -import { User } from "@/models/User/User.js"; -import type { InvoiceItem } from "@/models/Invoice/Invoice.js"; +import { User } from "@/models/User"; +import type { InvoiceItem } from "@/models/Invoice"; /** * Notify assignee of new Invoice when `NewInvoice` event is emitted. diff --git a/src/events/onInvoiceDeleted/notifyAssigneeDeletedInvoice.test.ts b/src/events/onInvoiceDeleted/notifyAssigneeDeletedInvoice.test.ts index 63fa3946..0b08669e 100644 --- a/src/events/onInvoiceDeleted/notifyAssigneeDeletedInvoice.test.ts +++ b/src/events/onInvoiceDeleted/notifyAssigneeDeletedInvoice.test.ts @@ -1,8 +1,8 @@ import { InvoicePushNotification } from "@/events/pushNotifications/InvoicePushNotification.js"; import { lambdaClient } from "@/lib/lambdaClient/lambdaClient.js"; -import { User, type UserItem } from "@/models/User/User.js"; +import { User, type UserItem } from "@/models/User"; import { notifyAssigneeDeletedInvoice } from "./notifyAssigneeDeletedInvoice.js"; -import type { InvoiceItem } from "@/models/Invoice/Invoice.js"; +import type { InvoiceItem } from "@/models/Invoice"; describe("notifyAssigneeDeletedInvoice", () => { test("sends a push notification to the assignee when the assignee has an expoPushToken", async () => { diff --git a/src/events/onInvoiceDeleted/notifyAssigneeDeletedInvoice.ts b/src/events/onInvoiceDeleted/notifyAssigneeDeletedInvoice.ts index 971e89c3..fd26462d 100644 --- a/src/events/onInvoiceDeleted/notifyAssigneeDeletedInvoice.ts +++ b/src/events/onInvoiceDeleted/notifyAssigneeDeletedInvoice.ts @@ -1,7 +1,7 @@ import { InvoicePushNotification } from "@/events/pushNotifications"; import { lambdaClient } from "@/lib/lambdaClient"; -import { User } from "@/models/User/User.js"; -import type { InvoiceItem } from "@/models/Invoice/Invoice.js"; +import { User } from "@/models/User"; +import type { InvoiceItem } from "@/models/Invoice"; /** * Notify assignee of deleted Invoice when `InvoiceDeleted` event is emitted. diff --git a/src/events/onInvoicePaid/notifyAssignorPaidInvoice.test.ts b/src/events/onInvoicePaid/notifyAssignorPaidInvoice.test.ts index 5aee4ebf..6a92ab04 100644 --- a/src/events/onInvoicePaid/notifyAssignorPaidInvoice.test.ts +++ b/src/events/onInvoicePaid/notifyAssignorPaidInvoice.test.ts @@ -1,8 +1,8 @@ import { InvoicePushNotification } from "@/events/pushNotifications"; import { lambdaClient } from "@/lib/lambdaClient"; -import { User, type UserItem } from "@/models/User/User.js"; +import { User, type UserItem } from "@/models/User"; import { notifyAssignorPaidInvoice } from "./notifyAssignorPaidInvoice.js"; -import type { InvoiceItem } from "@/models/Invoice/Invoice.js"; +import type { InvoiceItem } from "@/models/Invoice"; describe("notifyAssignorPaidInvoice", () => { test("sends a push notification to the assignor when the assignor has an expoPushToken", async () => { diff --git a/src/events/onInvoicePaid/notifyAssignorPaidInvoice.ts b/src/events/onInvoicePaid/notifyAssignorPaidInvoice.ts index e764afad..3909e54a 100644 --- a/src/events/onInvoicePaid/notifyAssignorPaidInvoice.ts +++ b/src/events/onInvoicePaid/notifyAssignorPaidInvoice.ts @@ -1,7 +1,7 @@ import { InvoicePushNotification } from "@/events/pushNotifications"; import { lambdaClient } from "@/lib/lambdaClient"; -import { User } from "@/models/User/User.js"; -import type { InvoiceItem } from "@/models/Invoice/Invoice.js"; +import { User } from "@/models/User"; +import type { InvoiceItem } from "@/models/Invoice"; /** * Notify assignor of paid Invoice when `InvoicePaid` event is emitted. diff --git a/src/events/onInvoiceUpdated/notifyAssigneeUpdatedInvoice.test.ts b/src/events/onInvoiceUpdated/notifyAssigneeUpdatedInvoice.test.ts index 6d2658a5..d86cb688 100644 --- a/src/events/onInvoiceUpdated/notifyAssigneeUpdatedInvoice.test.ts +++ b/src/events/onInvoiceUpdated/notifyAssigneeUpdatedInvoice.test.ts @@ -1,8 +1,8 @@ import { InvoicePushNotification } from "@/events/pushNotifications"; import { lambdaClient } from "@/lib/lambdaClient"; -import { User, type UserItem } from "@/models/User/User.js"; +import { User, type UserItem } from "@/models/User"; import { notifyAssigneeUpdatedInvoice } from "./notifyAssigneeUpdatedInvoice.js"; -import type { InvoiceItem } from "@/models/Invoice/Invoice.js"; +import type { InvoiceItem } from "@/models/Invoice"; describe("notifyAssigneeUpdatedInvoice", () => { test("sends a push notification to the assignee when the assignee has an expoPushToken", async () => { diff --git a/src/events/onInvoiceUpdated/notifyAssigneeUpdatedInvoice.ts b/src/events/onInvoiceUpdated/notifyAssigneeUpdatedInvoice.ts index 223132b5..825ac0e7 100644 --- a/src/events/onInvoiceUpdated/notifyAssigneeUpdatedInvoice.ts +++ b/src/events/onInvoiceUpdated/notifyAssigneeUpdatedInvoice.ts @@ -1,7 +1,7 @@ import { InvoicePushNotification } from "@/events/pushNotifications"; import { lambdaClient } from "@/lib/lambdaClient"; -import { User } from "@/models/User/User.js"; -import type { InvoiceItem } from "@/models/Invoice/Invoice.js"; +import { User } from "@/models/User"; +import type { InvoiceItem } from "@/models/Invoice"; /** * Notify assignee of updated Invoice when `InvoiceUpdated` event is emitted. diff --git a/src/events/onNewUser/sendWelcomeEmail.test.ts b/src/events/onNewUser/sendWelcomeEmail.test.ts index 6622a18b..c90909a4 100644 --- a/src/events/onNewUser/sendWelcomeEmail.test.ts +++ b/src/events/onNewUser/sendWelcomeEmail.test.ts @@ -1,28 +1,49 @@ -import { lambdaClient } from "@/lib/lambdaClient"; +import { pinpointClient } from "@/lib/pinpointClient"; import { sendWelcomeEmail } from "./sendWelcomeEmail.js"; -import type { UserItem } from "@/models/User/User.js"; +import type { UserItem } from "@/models/User"; describe("sendWelcomeEmail", () => { - test("invokes lambdaClient with correct arguments when newUser is valid", async () => { + test("invokes pinpointClient with correct arguments when newUser is valid", async () => { const newUser = { id: "USER#123", handle: "@test_user", email: "test_user@example.com", + profile: { + displayName: "Test User", + }, } as UserItem; - const invokeEventSpy = vi.spyOn(lambdaClient, "invokeEvent"); + + const sendMessagesSpy = vi.spyOn(pinpointClient, "sendMessages"); const result = await sendWelcomeEmail(newUser); expect(result).toBeUndefined(); - expect(invokeEventSpy).toHaveBeenCalledWith("SendWelcomeEmail", newUser); + expect(sendMessagesSpy).toHaveBeenCalledWith( + expect.objectContaining({ + to: newUser.email, + ChannelType: "EMAIL", + TemplateConfiguration: { + EmailTemplate: { + Name: "new-user-welcome-email", + }, + }, + MessageConfiguration: { + EmailMessage: { + Substitutions: { + recipientDisplayName: [newUser.profile.displayName], + }, + }, + }, + }) + ); }); test("does not invoke an event when newUser is undefined", async () => { - const invokeEventSpy = vi.spyOn(lambdaClient, "invokeEvent"); + const sendMessagesSpy = vi.spyOn(pinpointClient, "sendMessages"); const result = await sendWelcomeEmail(); expect(result).toBeUndefined(); - expect(invokeEventSpy).not.toHaveBeenCalled(); + expect(sendMessagesSpy).not.toHaveBeenCalled(); }); }); diff --git a/src/events/onNewUser/sendWelcomeEmail.ts b/src/events/onNewUser/sendWelcomeEmail.ts index e795da13..d5331343 100644 --- a/src/events/onNewUser/sendWelcomeEmail.ts +++ b/src/events/onNewUser/sendWelcomeEmail.ts @@ -1,5 +1,5 @@ -import { lambdaClient } from "@/lib/lambdaClient"; -import type { UserItem } from "@/models/User/User.js"; +import { pinpointClient } from "@/lib/pinpointClient"; +import type { UserItem } from "@/models/User"; /** * Send welcome email to new User when `NewUser` event is emitted. @@ -10,9 +10,20 @@ import type { UserItem } from "@/models/User/User.js"; export const sendWelcomeEmail = async (newUser?: UserItem) => { if (!newUser) return; - await lambdaClient.invokeEvent("SendWelcomeEmail", { - id: newUser.id, - handle: newUser.handle, - email: newUser.email, + await pinpointClient.sendMessages({ + to: newUser.email, + ChannelType: "EMAIL", + TemplateConfiguration: { + EmailTemplate: { + Name: "new-user-welcome-email", + }, + }, + MessageConfiguration: { + EmailMessage: { + Substitutions: { + recipientDisplayName: [newUser.profile.displayName], + }, + }, + }, }); }; diff --git a/src/events/onWorkOrderCancelled/notifyAssigneeCancelledWO.test.ts b/src/events/onWorkOrderCancelled/notifyAssigneeCancelledWO.test.ts index 2a4ae161..da2e654e 100644 --- a/src/events/onWorkOrderCancelled/notifyAssigneeCancelledWO.test.ts +++ b/src/events/onWorkOrderCancelled/notifyAssigneeCancelledWO.test.ts @@ -1,8 +1,8 @@ import { WorkOrderPushNotification } from "@/events/pushNotifications"; import { lambdaClient } from "@/lib/lambdaClient"; -import { User, type UserItem } from "@/models/User/User.js"; +import { User, type UserItem } from "@/models/User"; import { notifyAssigneeCancelledWO } from "./notifyAssigneeCancelledWO.js"; -import type { WorkOrderItem } from "@/models/WorkOrder/WorkOrder.js"; +import type { WorkOrderItem } from "@/models/WorkOrder"; describe("notifyAssigneeCancelledWO", () => { test("sends a push notification to the assignee when the assignee has an expoPushToken", async () => { diff --git a/src/events/onWorkOrderCancelled/notifyAssigneeCancelledWO.ts b/src/events/onWorkOrderCancelled/notifyAssigneeCancelledWO.ts index 5046f883..36f8210b 100644 --- a/src/events/onWorkOrderCancelled/notifyAssigneeCancelledWO.ts +++ b/src/events/onWorkOrderCancelled/notifyAssigneeCancelledWO.ts @@ -1,7 +1,7 @@ import { WorkOrderPushNotification } from "@/events/pushNotifications/WorkOrderPushNotification.js"; import { lambdaClient } from "@/lib/lambdaClient/lambdaClient.js"; -import { User } from "@/models/User/User.js"; -import type { WorkOrderItem } from "@/models/WorkOrder/WorkOrder.js"; +import { User } from "@/models/User"; +import type { WorkOrderItem } from "@/models/WorkOrder"; /** * Notify assignee of cancelled WorkOrder when `WorkOrderCancelled` event is emitted. @@ -13,7 +13,7 @@ export const notifyAssigneeCancelledWO = async (cancelledWO?: WorkOrderItem) => if (!cancelledWO) return; // If new WorkOrder was UNASSIGNED, return. - if (!cancelledWO?.assignedToUserID) return; + if (!cancelledWO.assignedToUserID) return; const assigneeUser = await User.getItem({ id: cancelledWO.assignedToUserID }); diff --git a/src/events/onWorkOrderCompleted/notifyAssignorCompletedWO.test.ts b/src/events/onWorkOrderCompleted/notifyAssignorCompletedWO.test.ts index a5018101..a8f15996 100644 --- a/src/events/onWorkOrderCompleted/notifyAssignorCompletedWO.test.ts +++ b/src/events/onWorkOrderCompleted/notifyAssignorCompletedWO.test.ts @@ -1,8 +1,8 @@ import { WorkOrderPushNotification } from "@/events/pushNotifications"; import { lambdaClient } from "@/lib/lambdaClient"; -import { User, type UserItem } from "@/models/User/User.js"; +import { User, type UserItem } from "@/models/User"; import { notifyAssignorCompletedWO } from "./notifyAssignorCompletedWO.js"; -import type { WorkOrderItem } from "@/models/WorkOrder/WorkOrder.js"; +import type { WorkOrderItem } from "@/models/WorkOrder"; describe("notifyAssignorCompletedWO", () => { test("sends a push notification to the assignor when the assignor has an expoPushToken", async () => { diff --git a/src/events/onWorkOrderCompleted/notifyAssignorCompletedWO.ts b/src/events/onWorkOrderCompleted/notifyAssignorCompletedWO.ts index 1a618adc..dffbf469 100644 --- a/src/events/onWorkOrderCompleted/notifyAssignorCompletedWO.ts +++ b/src/events/onWorkOrderCompleted/notifyAssignorCompletedWO.ts @@ -1,7 +1,7 @@ import { WorkOrderPushNotification } from "@/events/pushNotifications"; import { lambdaClient } from "@/lib/lambdaClient"; -import { User } from "@/models/User/User.js"; -import type { WorkOrderItem } from "@/models/WorkOrder/WorkOrder.js"; +import { User } from "@/models/User"; +import type { WorkOrderItem } from "@/models/WorkOrder"; /** * Notify assignor of completed WorkOrder when `WorkOrderCompleted` event is emitted. diff --git a/src/events/onWorkOrderCreated/notifyAssigneeNewWO.test.ts b/src/events/onWorkOrderCreated/notifyAssigneeNewWO.test.ts index f1531bd9..5d9d2ebe 100644 --- a/src/events/onWorkOrderCreated/notifyAssigneeNewWO.test.ts +++ b/src/events/onWorkOrderCreated/notifyAssigneeNewWO.test.ts @@ -1,8 +1,8 @@ import { WorkOrderPushNotification } from "@/events/pushNotifications"; import { lambdaClient } from "@/lib/lambdaClient"; -import { User, type UserItem } from "@/models/User/User.js"; +import { User, type UserItem } from "@/models/User"; import { notifyAssigneeNewWO } from "./notifyAssigneeNewWO.js"; -import type { WorkOrderItem } from "@/models/WorkOrder/WorkOrder.js"; +import type { WorkOrderItem } from "@/models/WorkOrder"; describe("notifyAssigneeNewWO", () => { test("sends a push notification to the assignee when the assignee has an expoPushToken", async () => { diff --git a/src/events/onWorkOrderCreated/notifyAssigneeNewWO.ts b/src/events/onWorkOrderCreated/notifyAssigneeNewWO.ts index bc83a086..281809b5 100644 --- a/src/events/onWorkOrderCreated/notifyAssigneeNewWO.ts +++ b/src/events/onWorkOrderCreated/notifyAssigneeNewWO.ts @@ -1,7 +1,7 @@ import { WorkOrderPushNotification } from "@/events/pushNotifications"; import { lambdaClient } from "@/lib/lambdaClient"; -import { User } from "@/models/User/User.js"; -import type { WorkOrderItem } from "@/models/WorkOrder/WorkOrder.js"; +import { User } from "@/models/User"; +import type { WorkOrderItem } from "@/models/WorkOrder"; /** * Notify assignee of new WorkOrder when `WorkOrderAssigned` event is emitted. @@ -13,7 +13,7 @@ export const notifyAssigneeNewWO = async (newWO?: WorkOrderItem) => { if (!newWO) return; // If new WorkOrder is UNASSIGNED, return. - if (!newWO?.assignedToUserID) return; + if (!newWO.assignedToUserID) return; const assigneeUser = await User.getItem({ id: newWO.assignedToUserID }); diff --git a/src/events/onWorkOrderUpdated/notifyAssigneeUpdatedWO.test.ts b/src/events/onWorkOrderUpdated/notifyAssigneeUpdatedWO.test.ts index d04798c4..b861f4b3 100644 --- a/src/events/onWorkOrderUpdated/notifyAssigneeUpdatedWO.test.ts +++ b/src/events/onWorkOrderUpdated/notifyAssigneeUpdatedWO.test.ts @@ -1,8 +1,8 @@ import { WorkOrderPushNotification } from "@/events/pushNotifications"; import { lambdaClient } from "@/lib/lambdaClient"; -import { User, type UserItem } from "@/models/User/User.js"; +import { User, type UserItem } from "@/models/User"; import { notifyAssigneeUpdatedWO } from "./notifyAssigneeUpdatedWO.js"; -import type { WorkOrderItem } from "@/models/WorkOrder/WorkOrder.js"; +import type { WorkOrderItem } from "@/models/WorkOrder"; describe("notifyAssigneeUpdatedWO", () => { test("does not create/send any PushNotifications when there are no assignees", async () => { diff --git a/src/events/onWorkOrderUpdated/notifyAssigneeUpdatedWO.ts b/src/events/onWorkOrderUpdated/notifyAssigneeUpdatedWO.ts index 3518556b..9a1e3450 100644 --- a/src/events/onWorkOrderUpdated/notifyAssigneeUpdatedWO.ts +++ b/src/events/onWorkOrderUpdated/notifyAssigneeUpdatedWO.ts @@ -1,7 +1,7 @@ import { WorkOrderPushNotification } from "@/events/pushNotifications"; import { lambdaClient } from "@/lib/lambdaClient"; -import { User } from "@/models/User/User.js"; -import type { WorkOrderItem } from "@/models/WorkOrder/WorkOrder.js"; +import { User } from "@/models/User"; +import type { WorkOrderItem } from "@/models/WorkOrder"; /** * Sends push notifications to Users when a WorkOrder is updated. Determines which Users @@ -105,9 +105,8 @@ export const notifyAssigneeUpdatedWO = async ( ); // If there are any deliverable push notifications, send them to the PushService - if (deliverablePNs.length > 0) { + if (deliverablePNs.length > 0) // Submit deliverable push msgs as the PushService Lambda fn payload await lambdaClient.invokeEvent("PushNotificationService", deliverablePNs); - } } }; diff --git a/src/events/pushNotifications/InvoicePushNotification.ts b/src/events/pushNotifications/InvoicePushNotification.ts index 4eb32dec..cd3ec686 100644 --- a/src/events/pushNotifications/InvoicePushNotification.ts +++ b/src/events/pushNotifications/InvoicePushNotification.ts @@ -1,5 +1,5 @@ import { PushNotification, type PushNotificationRecipient } from "./PushNotification.js"; -import type { InvoiceItem } from "@/models/Invoice/Invoice.js"; +import type { InvoiceItem } from "@/models/Invoice"; /** * This class represents a push notification for an Invoice event. diff --git a/src/events/pushNotifications/PushNotification.ts b/src/events/pushNotifications/PushNotification.ts index 7c160f65..04de642d 100644 --- a/src/events/pushNotifications/PushNotification.ts +++ b/src/events/pushNotifications/PushNotification.ts @@ -1,5 +1,6 @@ import { ENV } from "@/server/env"; -import type { UserItem } from "@/models/User/User.js"; +import type { UserItem } from "@/models/User"; +import type { Primitive } from "type-fest"; /** * Represents a push notification. @@ -18,7 +19,7 @@ export class PushNotification { _apiEnv: typeof ENV.NODE_ENV; _pushEventName: string; _recipientUser: PushNotificationRecipient["id"]; - [key: string]: any; + [key: string]: NonNullable; }; /** @@ -55,5 +56,5 @@ export class PushNotification { * User-fields which define the {@link PushNotification} recipient. */ export type PushNotificationRecipient = Pick & { - [key: string]: any; + [key: string]: Primitive | object; }; diff --git a/src/events/pushNotifications/WorkOrderPushNotification.ts b/src/events/pushNotifications/WorkOrderPushNotification.ts index 624916c4..bbc50e2e 100644 --- a/src/events/pushNotifications/WorkOrderPushNotification.ts +++ b/src/events/pushNotifications/WorkOrderPushNotification.ts @@ -1,5 +1,5 @@ import { PushNotification, type PushNotificationRecipient } from "./PushNotification.js"; -import type { WorkOrderItem } from "@/models/WorkOrder/WorkOrder.js"; +import type { WorkOrderItem } from "@/models/WorkOrder"; /** * This class represents a push notification for a WorkOrder event. diff --git a/src/expressApp.ts b/src/expressApp.ts index f2e827db..ba854fb7 100644 --- a/src/expressApp.ts +++ b/src/expressApp.ts @@ -1,15 +1,13 @@ import { expressMiddleware } from "@apollo/server/express4"; import * as Sentry from "@sentry/node"; -import express from "express"; -import { apolloServer } from "@/apolloServer.js"; +import express, { type Express } from "express"; +import { apolloServer, getAuthenticatedApolloContext } from "@/apolloServer.js"; import { corsMW, - errorHandler, - handle404, - logReqReceived, - sendRESTJsonResponse, setSecureHttpHeaders, - validateGqlReqContext, + logReqReceived, + handle404, + errorHandler, } from "@/middleware"; import { adminRouter, @@ -17,63 +15,71 @@ import { connectRouter, subscriptionsRouter, webhooksRouter, -} from "@/routers"; +} from "@/routes"; import { ENV } from "@/server/env"; /** - * The express app for REST requests and the GraphQL entry point. + * The express app for API REST requests as well as the GraphQL entry point. * * > - `view cache` is always disabled since this app doesn't return HTML * > - `X-Powered-By` header is always disabled for security * > - `trust proxy` is enabled in deployed envs so the correct IP can be logged (not the LB's) - * @see https://expressjs.com/en/4x/api.html#app.settings.table + * + * See https://expressjs.com/en/4x/api.html#app.settings.table */ export const expressApp = express() .disable("view cache") .disable("x-powered-by") - .set("trust proxy", ENV.IS_DEPLOYED_ENV); - -// SENTRY REQUEST-HANDLER (must be first middleware) -expressApp.use( - Sentry.Handlers.requestHandler({ - // Keys to be extracted from req object and attached to the Sentry scope: - request: ["ip", "data", "headers", "method", "query_string", "url"], - ip: true, - }) -); + .set("trust proxy", ENV.IS_DEPLOYED_ENV) as Express & { + /** When called, this function mounts all middleware and route handlers for the express app. */ + setupMiddleware: () => void; +}; -// LOG ALL REQUESTS -expressApp.use(logReqReceived); +expressApp.setupMiddleware = () => { + // SENTRY REQUEST-HANDLER (must be first middleware) + expressApp.use( + Sentry.Handlers.requestHandler({ + // Keys to be extracted from req object and attached to the Sentry scope: + request: ["ip", "data", "headers", "method", "query_string", "url"], + ip: true, + }) + ); -// SECURITY -expressApp.use(corsMW, setSecureHttpHeaders); + // LOG ALL REQUESTS + expressApp.use(logReqReceived); -// BODY-PARSING (admin and webhooks routers handle their own body parsing) -expressApp.use([/^\/api$/, /^\/api\/(auth|connect|subscriptions)/], express.json()); + // SECURITY + expressApp.use(corsMW, setSecureHttpHeaders); -// REST ROUTE HANDLERS -expressApp.use("/api/admin", adminRouter); -expressApp.use("/api/auth", authRouter); -expressApp.use("/api/connect", connectRouter); -expressApp.use("/api/subscriptions", subscriptionsRouter); -expressApp.use("/api/webhooks", webhooksRouter); + // BODY-PARSING (webhooks routes handle their own body parsing) + expressApp.use( + /^\/api\/?((?!webhooks)\w+)?/, + express.json({ + type: ["application/json", "application/csp-report", "application/reports+json"], + }) + ); -// REST RESPONSE HANDLER (admin and webhooks routers handle their own responses) -expressApp.use(/^\/api\/(auth|connect|subscriptions)/, sendRESTJsonResponse); + // REST ROUTE HANDLERS + expressApp.use("/api/admin", adminRouter); + expressApp.use("/api/auth", authRouter); + expressApp.use("/api/connect", connectRouter); + expressApp.use("/api/subscriptions", subscriptionsRouter); + expressApp.use("/api/webhooks", webhooksRouter); -// APOLLO SERVER (root path: /api) -expressApp.use( - "/api", - expressMiddleware(apolloServer, { - context: validateGqlReqContext, - }) -); + // GRAPHQL API ENTRYPOINT (root path: /api) + expressApp.use( + "/api", + expressMiddleware(apolloServer, { + context: getAuthenticatedApolloContext, + }) + ); -// SENTRY ERROR-HANDLER (must be before any other error-mw and after all controllers) -expressApp.use(Sentry.Handlers.errorHandler()); + // SENTRY ERROR-HANDLER (must be before any other error-mw and after all controllers) + expressApp.use(Sentry.Handlers.errorHandler()); -// HANDLE NON-EXISTENT REQUEST ROUTES -expressApp.use(handle404); + // HANDLE NON-EXISTENT REQUEST ROUTES + expressApp.use(handle404); -// UNIVERSAL FALLBACK ERROR HANDLER -expressApp.use(errorHandler); + // UNIVERSAL FALLBACK ERROR HANDLER + expressApp.use(errorHandler); +}; diff --git a/src/graphql/AuthToken/typeDefs.ts b/src/graphql/AuthToken/typeDefs.ts deleted file mode 100644 index 2b1d804f..00000000 --- a/src/graphql/AuthToken/typeDefs.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` - type AuthTokenPayload { - id: ID! - handle: String! - email: String! - phone: String - profile: Profile! - stripeCustomerID: String! - subscription: AuthTokenPayloadSubscriptionInfo - stripeConnectAccount: AuthTokenPayloadStripeConnectAccountInfo - createdAt: DateTime! - updatedAt: DateTime! - } - - type AuthTokenPayloadSubscriptionInfo { - id: ID! - status: SubscriptionStatus! - currentPeriodEnd: DateTime! - } - - type AuthTokenPayloadStripeConnectAccountInfo { - id: ID! - detailsSubmitted: Boolean! - chargesEnabled: Boolean! - payoutsEnabled: Boolean! - } -`; diff --git a/src/graphql/Checklist/helpers.ts b/src/graphql/Checklist/helpers.ts new file mode 100644 index 00000000..9ffde0f5 --- /dev/null +++ b/src/graphql/Checklist/helpers.ts @@ -0,0 +1,25 @@ +import { sanitizeText, isValidText } from "@nerdware/ts-string-helpers"; +import { z as zod } from "zod"; +import type { CreateChecklistItemInput, UpdateChecklistItemInput } from "@/types/graphql.js"; +import type { ZodObjectWithShape } from "@/types/zod.js"; + +/** + * Zod schema for {@link CreateChecklistItemInput} objects. + */ +export const createChecklistItemZodSchema = zod + .object({ + description: zod.string().transform(sanitizeText).refine(isValidText), + isCompleted: zod + .boolean() + .nullish() + .default(false) + .transform((value) => !!value), + }) + .strict() satisfies ZodObjectWithShape; + +/** + * Zod schema for {@link UpdateChecklistItemInput} objects. + */ +export const updateChecklistItemZodSchema = createChecklistItemZodSchema.extend({ + id: zod.string().nullish().default(null), +}) satisfies ZodObjectWithShape; diff --git a/src/graphql/Checklist/typeDefs.ts b/src/graphql/Checklist/typeDefs.ts index 34460c05..6f5dc4de 100644 --- a/src/graphql/Checklist/typeDefs.ts +++ b/src/graphql/Checklist/typeDefs.ts @@ -1,6 +1,4 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` +export const typeDefs = `#graphql type ChecklistItem { id: ID! description: String! diff --git a/src/graphql/Contact/resolvers.ts b/src/graphql/Contact/resolvers.ts index bf43113d..47565976 100644 --- a/src/graphql/Contact/resolvers.ts +++ b/src/graphql/Contact/resolvers.ts @@ -1,20 +1,20 @@ -import { DeleteMutationResponse } from "@/graphql/_common"; -import { usersCache } from "@/lib/cache/usersCache.js"; -import { Contact } from "@/models/Contact/Contact.js"; -import { User } from "@/models/User/User.js"; -import { GqlUserInputError, GqlInternalServerError } from "@/utils/httpErrors.js"; +import { DeleteMutationResponse } from "@/graphql/_responses"; +import { Contact, contactModelHelpers } from "@/models/Contact"; +import { userModelHelpers } from "@/models/User"; +import { ContactService } from "@/services/ContactService"; +import { UserService } from "@/services/UserService"; import type { Resolvers } from "@/types/graphql.js"; -export const resolvers: Partial = { +export const resolvers: Resolvers = { Query: { contact: async (_parent, { contactID }, { user }) => { - const contact = await Contact.getItem({ userID: user.id, id: contactID }); + // Sanitize contactID + contactID = contactModelHelpers.id.sanitizeAndValidate(contactID); - if (!contact) { - throw new GqlUserInputError("A contact with the provided ID could not be found."); - } - - return contact; + return await ContactService.findContactByID({ + authenticatedUserID: user.id, + contactID, + }); }, myContacts: async (_parent, _args, { user }) => { return await Contact.query({ @@ -27,59 +27,34 @@ export const resolvers: Partial = { }, Mutation: { createContact: async (_parent, { contactUserID }, { user }) => { - // First, ensure the user hasn't somehow sent their own ID - if (contactUserID.toUpperCase() === user.id.toUpperCase()) { - throw new GqlUserInputError("Can not add yourself as a contact"); - } - - const requestedUser = await User.getItem({ id: contactUserID }); + // Sanitize contactUserID + contactUserID = userModelHelpers.id.sanitizeAndValidate(contactUserID); - if (!requestedUser) throw new GqlUserInputError("Requested user not found."); - - // create method won't overwrite existing, if Contact already exists. - return await Contact.createItem({ - userID: user.id, - contactUserID: requestedUser.id, - handle: requestedUser.handle, + return await ContactService.createContact({ + authenticatedUserID: user.id, + contactUserID, }); }, deleteContact: async (_parent, { contactID }, { user }) => { - // Test to ensure `contactID` is a valid contact ID - if (!Contact.isValidID(contactID)) throw new GqlUserInputError("Invalid contact ID."); + // Sanitize contactID + contactID = contactModelHelpers.id.sanitizeAndValidate(contactID); await Contact.deleteItem({ userID: user.id, id: contactID }); - return new DeleteMutationResponse({ id: contactID, wasDeleted: true }); + return new DeleteMutationResponse({ success: true, id: contactID }); }, }, Contact: { email: async (parent) => { - let user = usersCache.get(parent.handle); - - user ||= await User.getItem({ id: parent.contactUserID }); - - if (!user?.email) { - throw new GqlInternalServerError("Contact email could not be found."); - } - + const user = await UserService.getUserByHandle(parent); return user.email; }, phone: async (parent) => { - let user = usersCache.get(parent.handle); - - user ||= await User.getItem({ id: parent.contactUserID }); - - return user?.phone ?? null; + const user = await UserService.getUserByHandle(parent); + return user.phone ?? null; }, profile: async (parent) => { - let user = usersCache.get(parent.handle); - - user ||= await User.getItem({ id: parent.contactUserID }); - - if (!user?.profile) { - throw new GqlInternalServerError("Contact profile could not be found."); - } - + const user = await UserService.getUserByHandle(parent); return user.profile; }, }, diff --git a/src/graphql/Contact/typeDefs.ts b/src/graphql/Contact/typeDefs.ts index 662504e0..81ee3533 100644 --- a/src/graphql/Contact/typeDefs.ts +++ b/src/graphql/Contact/typeDefs.ts @@ -1,13 +1,11 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` +export const typeDefs = `#graphql """ Contact is a type which is simply a concrete implementation of the publicly - accessible User fields defined in the FixitUser interface. The Contact type is - meant to ensure that private User fields are not available to anyone other than - the User who owns the data. + accessible user fields defined in the PublicUserFields interface. The Contact + type represents a user's contact, and is used to manage a user's contacts. """ - type Contact implements FixitUser { + type Contact implements PublicUserFields { + # Contact.id format: "CONTACT#{contactUserID}" "Contact ID internally identifies a user's contact" id: ID! "Public-facing handle identifies users to other users (e.g., '@joe')" @@ -24,6 +22,8 @@ export const typeDefs = gql` updatedAt: DateTime! } + + extend type Query { contact(contactID: ID!): Contact! myContacts: [Contact!]! @@ -34,3 +34,31 @@ export const typeDefs = gql` deleteContact(contactID: ID!): DeleteMutationResponse! } `; + +/* IDEA ContactStatus: NORMAL, BLOCKED + + NORMAL + The NORMAL status would indicate that the contact relationship is active. + + BLOCKED + The BLOCKED status would prevent two users from having any further interactions with one another. + + ### Implications for WorkOrders + - The users would not be able to assign WorkOrders to one another. + - Any existing WorkOrders between the users which have a non-terminal "status" (anything other + than COMPLETE or CANCELLED) would incur the following changes: + - "status" would be changed to UNASSIGNED. + - "assignedTo" would be changed to null. + + ### Implications for Invoices + - The users would not be able assign Invoices to one another. + - Any existing Invoices between the users which have a non-terminal "status" (anything other + than CLOSED) would require special handling: + - Another enum-value could be added to the InvoiceStatus enum to handle this case, e.g., + CANCELLED/BLOCKED. + + ### Implications for Contacts + - The user who initiated the BLOCKED status would be able to see the blocked Contact in their + list of blocked Contacts, but the blocked Contact would not be able to see the blocker in + any Contact list, and would not see the blocker in any search results. +*/ diff --git a/src/graphql/FixitUser/typeDefs.ts b/src/graphql/FixitUser/typeDefs.ts deleted file mode 100644 index aa274d9a..00000000 --- a/src/graphql/FixitUser/typeDefs.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` - """ - FixitUser is an interface which defines publicly-accessible User fields. This - interface has two concrete implementations: Contact, which is simply a concrete - implementation of the same publicly-available fields, and User, which adds private - fields which are not accessible to other users. - """ - interface FixitUser { - "User ID internally identifies individual User accounts" - id: ID! - "Public-facing handle identifies users to other users (e.g., '@joe')" - handle: String! - "Email address of either a User or Contact" - email: Email! - "Phone number of either a User or Contact" - phone: String - "Profile object of either a User or Contact" - profile: Profile! - createdAt: DateTime! - updatedAt: DateTime! - } -`; diff --git a/src/graphql/FixitUser/types.ts b/src/graphql/FixitUser/types.ts deleted file mode 100644 index 0989e986..00000000 --- a/src/graphql/FixitUser/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { User, Contact } from "@/types/graphql.js"; - -/** - * # This type is only for use in the `codegen` config file (see `/codegen.ts`) - * - * This type is only used in the `codegen` config file under `"mappers"` to ensure the - * `@graphql-codegen/typescript-resolvers` package resolves the `FixitUser` interface to - * the GQL schema-typeDef types, `User | Contact`, rather than the DB-model types - * `UserItem | ContactItem`. - */ -export type FixitUserCodegenInterface = User | Contact; diff --git a/src/graphql/GraphQLError/helpers.ts b/src/graphql/GraphQLError/helpers.ts new file mode 100644 index 00000000..9d435f3c --- /dev/null +++ b/src/graphql/GraphQLError/helpers.ts @@ -0,0 +1,86 @@ +import { unwrapResolverError } from "@apollo/server/errors"; +import { HTTP_ERROR_CONFIGS, type HttpError } from "@/utils/httpErrors.js"; +import { logger } from "@/utils/logger.js"; +import type { ApolloServerContext } from "@/apolloServer.js"; +import type { + GraphQLFormattedErrorWithExtensions, + GraphQLErrorCustomExtensions as GqlErrorExtensions, +} from "@/types/graphql.js"; +import type { ApolloServerOptions } from "@apollo/server"; + +/** + * ### ApolloServer `formatError` function + * + * This function formats errors for GQL client responses. + * The returned GraphQLFormattedError contains the following extensions: + * + * - `code` — GraphQLError code, e.g. `BAD_USER_INPUT`, the value for which is determined using the + * following order of precedence: + * + * 1. If the `gqlFormattedError` arg provided by Apollo contains a _truthy_ `extensions.code` value, + * it is retained (note: Apollo's typing implies `code` could be explicitly `undefined`). + * 2. If the original error contains an {@link HttpError} `statusCode` property, `code` is set to + * the associated `extensions.code` value (e.g. `BAD_USER_INPUT` for 400). + * 3. If no other `code` value is found, `code` is set to `INTERNAL_SERVER_ERROR`. + * + * - `http` — An object containing the following properties: + * + * - `status` — The HTTP status code associated with the error, e.g. 400, 401, 500, etc. + * The value for this property is determined using the following order of precedence: + * + * 1. If the `gqlFormattedError` arg contains `extensions.http.status`, it is retained. + * 2. If the original error contains a `status`, that value is used. + * 3. If no other `statusCode` value is found, `statusCode` is set to 500. + * + * See https://www.apollographql.com/docs/apollo-server/data/errors/#for-client-responses + */ +export const formatApolloError: NonNullable< + ApolloServerOptions["formatError"] +> = ( + gqlFormattedError, + originalErrorOrWrappedError // Apollo wraps the originalError in a GraphQLError if thrown from a resolver +): GraphQLFormattedErrorWithExtensions => { + // If the originalError is wrapped in a GraphQLError, unwrap it + const originalError = unwrapResolverError(originalErrorOrWrappedError) ?? {}; + + // See if the originalError has an HttpError `statusCode`, default to 500 + const { statusCode: httpErrorStatusCode = 500 } = + originalError as { statusCode?: HttpError["statusCode"] }; // prettier-ignore + + // If the statusCode >= 500, log the error + if (httpErrorStatusCode >= 500) { + logger.error( + { gqlFormattedError, originalErrorOrWrappedError }, + `[ApolloServer.formatError] INTERNAL SERVER ERROR` + ); + } + + // Look up the error config for the statusCode + const httpErrorGqlExtensions = HTTP_ERROR_CONFIGS[httpErrorStatusCode]?.gqlErrorExtensions; + + // Check for unhandled statusCodes (should never happen, but just in case) + if (!httpErrorGqlExtensions) { + logger.error( + { gqlFormattedError, originalErrorOrWrappedError }, + `[ApolloServer.formatError] UNEXPECTED HTTP STATUS CODE: "${httpErrorStatusCode}"` + ); + } + + const gqlFormattedErrorExts: Partial = gqlFormattedError.extensions ?? {}; + + // Must return a GraphQLFormattedError object + return { + ...gqlFormattedError, + // EXTS: Keep original if present, else try statusCode, else 500/INTERNAL_SERVER_ERROR. + extensions: { + // Apollo's typing implies `code` could be explicitly `undefined`, which is why spread syntax is not used here. + code: + gqlFormattedErrorExts.code ?? + httpErrorGqlExtensions?.code ?? + HTTP_ERROR_CONFIGS[500].gqlErrorExtensions.code, + http: { + status: gqlFormattedErrorExts.http?.status ?? httpErrorStatusCode, + }, + }, + }; +}; diff --git a/src/graphql/GraphQLError/typeDefs.ts b/src/graphql/GraphQLError/typeDefs.ts new file mode 100644 index 00000000..3597c973 --- /dev/null +++ b/src/graphql/GraphQLError/typeDefs.ts @@ -0,0 +1,51 @@ +export const typeDefs = `#graphql + """ + GraphQLError custom extensions for client responses. + See https://www.apollographql.com/docs/apollo-server/data/errors/#custom-errors + """ + type GraphQLErrorCustomExtensions { + code: GraphQLErrorCode! + http: GraphQLErrorCustomHttpExtension + } + + "GraphQLError 'extensions.code' values for client error responses" + enum GraphQLErrorCode { + """ + The GraphQLError 'extensions.code' value for 400-status errors. + > This code is an ApolloServer builtin — see [ApolloServerErrorCode][apollo-error-codes]. + [apollo-error-codes]: https://github.com/apollographql/apollo-server/blob/268687db591fed8293eeded1546ae2f8e6f2b6a7/packages/server/src/errors/index.ts + """ + BAD_USER_INPUT + "The GraphQLError 'extensions.code' value for 401-status errors." + AUTHENTICATION_REQUIRED + "The GraphQLError 'extensions.code' value for 402-status errors." + PAYMENT_REQUIRED + "The GraphQLError 'extensions.code' value for 403-status errors." + FORBIDDEN + "The GraphQLError 'extensions.code' value for 404-status errors." + RESOURCE_NOT_FOUND + """ + The GraphQLError 'extensions.code' value for 500-status errors. + > This code is an ApolloServer builtin — see [ApolloServerErrorCode][apollo-error-codes]. + [apollo-error-codes]: https://github.com/apollographql/apollo-server/blob/268687db591fed8293eeded1546ae2f8e6f2b6a7/packages/server/src/errors/index.ts + """ + INTERNAL_SERVER_ERROR + } + + """ + GraphQLError custom 'http' extension for providing client error responses + with traditional HTTP error status codes ('extensions.http.status'). + """ + type GraphQLErrorCustomHttpExtension { + """ + The HTTP status code for the error: + - 400 'Bad User Input' + - 401 'Authentication Required' + - 402 'Payment Required' + - 403 'Forbidden' + - 404 'Resource Not Found' + - 500 'Internal Server Error' + """ + status: Int! + } +`; diff --git a/src/graphql/Invite/resolvers.ts b/src/graphql/Invite/resolvers.ts index dbcf7999..864a1ebf 100644 --- a/src/graphql/Invite/resolvers.ts +++ b/src/graphql/Invite/resolvers.ts @@ -1,34 +1,72 @@ -import { isValidPhone, isValidEmail } from "@nerdware/ts-string-helpers"; -import { isString } from "@nerdware/ts-type-safety-utils"; -import { GenericSuccessResponse } from "@/graphql/_common"; -import { GqlUserInputError } from "@/utils/httpErrors.js"; +import { sanitizeEmail, isValidPhone, isValidEmail } from "@nerdware/ts-string-helpers"; +import { MutationResponse } from "@/graphql/_responses"; +import { pinpointClient } from "@/lib/pinpointClient"; +import { UserInputError } from "@/utils/httpErrors.js"; import type { Resolvers } from "@/types/graphql.js"; -export const resolvers: Partial = { +export const resolvers: Resolvers = { Mutation: { - createInvite: (_parent, { phoneOrEmail }) => { - if (!phoneOrEmail || !isString(phoneOrEmail)) { - throw new GqlUserInputError("Unable to create invite with the provided input."); - } + createInvite: async (_parent, { phoneOrEmail }, { user }) => { + // Sanitize phoneOrEmail + phoneOrEmail = sanitizeEmail(phoneOrEmail); // <-- works bc the fn allows digit chars + + // Get the sender's name for usage in the invite message template + const { + profile: { givenName, familyName, displayName }, + } = user; + + const inviteSenderDisplayName = + givenName && familyName ? `${givenName} ${familyName}` : givenName ?? displayName; + + // SEE IF THE ARG IS A PHONE NUMBER OR EMAIL ADDRESS + if (isValidPhone(phoneOrEmail)) { + const phone = phoneOrEmail; - // Determine if arg is a valid US phone or email address - const argType = isValidPhone(phoneOrEmail) - ? "phone" - : isValidEmail(phoneOrEmail) - ? "email" - : null; + // Send text SMS invite + await pinpointClient.sendMessages({ + to: phone, + ChannelType: "SMS", + TemplateConfiguration: { + SMSTemplate: { + Name: "fixit-user-invitation-sms", + }, + }, + MessageConfiguration: { + SMSMessage: { + Substitutions: { + senderDisplayName: [inviteSenderDisplayName], + }, + }, + }, + }); + } else if (isValidEmail(phoneOrEmail)) { + // Assign email (`as string` bc TS infers "not a string" due to `isValidPhone` check above) + const email = phoneOrEmail as string; - if (argType === "phone") { - // TODO Send text SMS invite - } else if (argType === "email") { - // TODO Send email invite + // Send email invite + await pinpointClient.sendMessages({ + to: email, + ChannelType: "EMAIL", + TemplateConfiguration: { + EmailTemplate: { + Name: "fixit-user-invitation-email", + }, + }, + MessageConfiguration: { + EmailMessage: { + Substitutions: { + senderDisplayName: [inviteSenderDisplayName], + }, + }, + }, + }); } else { - throw new GqlUserInputError( - "Unable to send invite - a valid phone number or email address must be provided." + throw new UserInputError( + "Unable to send invite — the provided input must be a valid phone number or email address." ); } - return new GenericSuccessResponse({ wasSuccessful: true }); + return new MutationResponse({ success: true }); }, }, }; diff --git a/src/graphql/Invite/typeDefs.ts b/src/graphql/Invite/typeDefs.ts index d05adbbe..dc1c41d7 100644 --- a/src/graphql/Invite/typeDefs.ts +++ b/src/graphql/Invite/typeDefs.ts @@ -1,7 +1,5 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` +export const typeDefs = `#graphql extend type Mutation { - createInvite(phoneOrEmail: String!): GenericSuccessResponse! + createInvite(phoneOrEmail: String!): MutationResponse! } `; diff --git a/src/graphql/Invoice/helpers.ts b/src/graphql/Invoice/helpers.ts new file mode 100644 index 00000000..dc7c4e93 --- /dev/null +++ b/src/graphql/Invoice/helpers.ts @@ -0,0 +1,28 @@ +import { isString } from "@nerdware/ts-type-safety-utils"; +import { z as zod } from "zod"; +import { userModelHelpers } from "@/models/User/helpers.js"; +import { workOrderModelHelpers as woModelHelpers } from "@/models/WorkOrder/helpers.js"; +import type { InvoiceInput } from "@/types/graphql.js"; +import type { ZodObjectWithShape } from "@/types/zod.js"; +import type { UndefinedOnPartialDeep } from "type-fest"; + +/** + * Zod schema for {@link InvoiceInput} objects. + */ +export const createInvoiceZodSchema = zod + .object({ + amount: zod.number().positive().int(), + assignedTo: zod + .string() + .transform(userModelHelpers.id.sanitize) + .refine(userModelHelpers.id.isValid, { message: "Invalid value for field: 'assignedTo'" }), + workOrderID: zod + .string() + .nullish() + .default(null) + .transform((value) => (isString(value) ? woModelHelpers.id.sanitize(value) : null)) + .refine((value) => (isString(value) ? woModelHelpers.id.isValid(value) : true), { + message: "Invalid value for field: 'workOrderID'", + }), + }) + .strict() satisfies ZodObjectWithShape>; diff --git a/src/graphql/Invoice/resolvers.ts b/src/graphql/Invoice/resolvers.ts index 07c18a36..8bac66a5 100644 --- a/src/graphql/Invoice/resolvers.ts +++ b/src/graphql/Invoice/resolvers.ts @@ -1,217 +1,89 @@ -import { eventEmitter } from "@/events/eventEmitter.js"; -import { DeleteMutationResponse } from "@/graphql/_common"; -import { - verifyUserIsAuthorizedToPerformThisUpdate, - formatAsGqlFixitUser, -} from "@/graphql/_helpers"; -import { stripe } from "@/lib/stripe/stripeClient.js"; -import { Invoice, type InvoiceItem } from "@/models/Invoice/Invoice.js"; -import { WorkOrder } from "@/models/WorkOrder/WorkOrder.js"; -import { GqlUserInputError, GqlForbiddenError } from "@/utils/httpErrors.js"; +import { DeleteMutationResponse } from "@/graphql/_responses/index.js"; +import { invoiceModelHelpers } from "@/models/Invoice/helpers.js"; +import { User } from "@/models/User"; +import { WorkOrder } from "@/models/WorkOrder"; +import { InvoiceService } from "@/services/InvoiceService"; +import { UserInputError } from "@/utils/httpErrors.js"; +import { createInvoiceZodSchema } from "./helpers.js"; import type { Resolvers } from "@/types/graphql.js"; -export const resolvers: Partial = { +export const resolvers: Resolvers = { Query: { invoice: async (_parent, { invoiceID }) => { - const [existingInv] = await Invoice.query({ - where: { id: invoiceID }, - limit: 1, - }); - - assertInvoiceWasFound(existingInv); + // Sanitize the provided invoiceID + invoiceID = invoiceModelHelpers.id.sanitizeAndValidate(invoiceID); - return existingInv; + return await InvoiceService.findInvoiceByID({ invoiceID }); }, myInvoices: async (_parent, _args, { user }) => { - // Query for all Invoices created by the authenticated User - const createdByUserQueryResults = await Invoice.query({ - where: { - createdByUserID: user.id, - id: { beginsWith: Invoice.SK_PREFIX }, - }, - }); - - // Query for all Invoices assigned to the authenticated User - const assignedToUserQueryResults = await Invoice.query({ - where: { - assignedToUserID: user.id, - id: { beginsWith: Invoice.SK_PREFIX }, - }, - }); - - // Map each query's results into the GraphQL schema's WorkOrder shape // TODO maybe let resolvers do this? - return { - createdByUser: await Promise.all( - createdByUserQueryResults.map(async (invCreatedByUser) => ({ - ...invCreatedByUser, - createdBy: { ...user }, - assignedTo: await formatAsGqlFixitUser({ id: invCreatedByUser.assignedToUserID }, user), - })) - ), - assignedToUser: await Promise.all( - assignedToUserQueryResults.map(async (invAssignedToUser) => ({ - ...invAssignedToUser, - createdBy: await formatAsGqlFixitUser({ id: invAssignedToUser.createdByUserID }, user), - assignedTo: { ...user }, - })) - ), - }; + return await InvoiceService.queryUsersInvoices({ authenticatedUser: user }); }, }, Mutation: { createInvoice: async (_parent, { invoice: invoiceInput }, { user }) => { - const createdInvoice = await Invoice.createItem({ + // Sanitize and validate the provided invoiceInput + invoiceInput = createInvoiceZodSchema.parse(invoiceInput); + + return await InvoiceService.createInvoice({ createdByUserID: user.id, - assignedToUserID: invoiceInput.assignedTo, - amount: invoiceInput.amount, - status: "OPEN", - ...(!!invoiceInput?.workOrderID && { - workOrderID: invoiceInput.workOrderID, - }), + ...invoiceInput, }); - - eventEmitter.emitInvoiceCreated(createdInvoice); - - return createdInvoice; }, updateInvoiceAmount: async (_parent, { invoiceID, amount }, { user }) => { - const [existingInv] = await Invoice.query({ where: { id: invoiceID }, limit: 1 }); - - verifyUserIsAuthorizedToPerformThisUpdate(existingInv, { - itemNotFoundErrorMessage: INVOICE_NOT_FOUND_ERROR_MSG, - idOfUserWhoCanPerformThisUpdate: existingInv?.createdByUserID, - authenticatedUserID: user.id, - forbiddenStatuses: { - CLOSED: "The requested invoice has already been closed.", - DISPUTED: - "The requested invoice has been disputed and cannot be updated at this time. " + - "Please contact the invoice's recipient for details and further assistance.", - }, + // Sanitize the provided invoiceID + invoiceID = invoiceModelHelpers.id.sanitizeAndValidate(invoiceID); + // Validate the provided amount + if (!invoiceModelHelpers.amount.isValid(amount)) + throw new UserInputError(`Invalid value for field: "amount"`); + + return await InvoiceService.updateInvoiceAmount({ + invoiceID, + amount, + authenticatedUser: user, }); - - const updatedInvoice = await Invoice.updateItem( - { id: existingInv.id, createdByUserID: existingInv.createdByUserID }, - { - update: { amount }, - } - ); - - eventEmitter.emitInvoiceUpdated(updatedInvoice); - - return updatedInvoice; }, payInvoice: async (_parent, { invoiceID }, { user }) => { - // IDEA Adding "assigneeUserDefaultPaymentMethodID" to Invoice would reduce queries. - const [existingInv] = await Invoice.query({ where: { id: invoiceID }, limit: 1 }); - - verifyUserIsAuthorizedToPerformThisUpdate(existingInv, { - itemNotFoundErrorMessage: INVOICE_NOT_FOUND_ERROR_MSG, - idOfUserWhoCanPerformThisUpdate: existingInv?.assignedToUserID, - authenticatedUserID: user.id, - forbiddenStatuses: { - CLOSED: "The requested invoice has already been closed.", - DISPUTED: "The requested invoice has been disputed and cannot be paid at this time.", - }, - }); - - // IDEA After default_payment_method is added to db, consider rm'ing this - const stripeCustomer = await stripe.customers.retrieve(user.stripeCustomerID); - - // Ensure the user hasn't removed themselves as a Stripe Customer - if (stripeCustomer.deleted) { - throw new GqlForbiddenError( - "Invalid Stripe Customer - please review your payment settings in your Stripe Dashboard and try again." - ); - } + // Sanitize the provided invoiceID + invoiceID = invoiceModelHelpers.id.sanitizeAndValidate(invoiceID); - const paymentIntent = await stripe.paymentIntents.create({ - customer: user.stripeCustomerID, - payment_method: stripeCustomer.invoice_settings.default_payment_method as string, // Cast from Stripe.StripePaymentMethod, which is only an object when expanded. - amount: existingInv.amount, - currency: "usd", // <-- Note: would need to be paramaterized for i18n - confirm: true, - on_behalf_of: user.stripeConnectAccount.id, - transfer_data: { - destination: user.stripeConnectAccount.id, - }, - }); - - const wasInvoiceSuccessfullyPaid = paymentIntent?.status === "succeeded"; - - const updatedInvoice = await Invoice.updateItem( - { id: existingInv.id, createdByUserID: existingInv.createdByUserID }, - { - update: { - stripePaymentIntentID: paymentIntent.id, - ...(wasInvoiceSuccessfullyPaid && { status: "CLOSED" }), - }, - } - ); - - if (wasInvoiceSuccessfullyPaid) eventEmitter.emitInvoicePaid(updatedInvoice); - - return updatedInvoice; + return await InvoiceService.payInvoice({ invoiceID, authenticatedUser: user }); }, deleteInvoice: async (_parent, { invoiceID }, { user }) => { - const [existingInv] = await Invoice.query({ - where: { id: invoiceID }, - limit: 1, - }); - - verifyUserIsAuthorizedToPerformThisUpdate(existingInv, { - itemNotFoundErrorMessage: INVOICE_NOT_FOUND_ERROR_MSG, - idOfUserWhoCanPerformThisUpdate: existingInv?.createdByUserID, - authenticatedUserID: user.id, - forbiddenStatuses: { - CLOSED: "The requested invoice has already been closed.", - DISPUTED: "The requested invoice has been disputed and cannot be deleted at this time.", - }, - }); + // Sanitize the provided invoiceID + invoiceID = invoiceModelHelpers.id.sanitizeAndValidate(invoiceID); - const deletedInvoice = await Invoice.deleteItem({ - createdByUserID: existingInv.createdByUserID, - id: existingInv.id, + const deletedInvoice = await InvoiceService.deleteInvoice({ + invoiceID, + authenticatedUser: user, }); - eventEmitter.emitInvoiceDeleted(deletedInvoice); - return new DeleteMutationResponse({ id: deletedInvoice.id, - wasDeleted: true, + success: true, }); }, }, Invoice: { - createdBy: async (invoice, _args, { user }) => { - return await formatAsGqlFixitUser({ id: invoice.createdByUserID }, user); - }, + createdBy: async ({ createdByUserID }, _args, { user }) => { + if (createdByUserID === user.id) return user; - assignedTo: async (invoice, _args, { user }) => { - return await formatAsGqlFixitUser({ id: invoice.assignedToUserID }, user); + const createdByUser = await User.getItem({ id: createdByUserID }); + + return createdByUser!; }, + assignedTo: async ({ assignedToUserID }, _args, { user }) => { + if (assignedToUserID === user.id) return user; - workOrder: async (invoice, _args) => { - if (!invoice?.workOrderID) return null; + const assignedToUser = await User.getItem({ id: assignedToUserID }); - const [workOrder] = await WorkOrder.query({ - where: { id: invoice.workOrderID }, - limit: 1, - }); + return assignedToUser!; + }, + workOrder: async ({ workOrderID }, _args) => { + if (!workOrderID) return null; + + const [workOrder] = await WorkOrder.query({ where: { id: workOrderID }, limit: 1 }); return workOrder ?? null; }, }, }; - -const INVOICE_NOT_FOUND_ERROR_MSG = "An invoice with the provided ID could not be found."; - -/** - * Asserts that an Invoice was found in the database. - */ -export function assertInvoiceWasFound( - invoice: Inv | undefined -): asserts invoice is NonNullable { - if (!invoice) { - throw new GqlUserInputError(INVOICE_NOT_FOUND_ERROR_MSG); - } -} diff --git a/src/graphql/Invoice/typeDefs.ts b/src/graphql/Invoice/typeDefs.ts index 109c6322..eddeb513 100644 --- a/src/graphql/Invoice/typeDefs.ts +++ b/src/graphql/Invoice/typeDefs.ts @@ -1,13 +1,11 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` +export const typeDefs = `#graphql type Invoice { - "(Immutable) Invoice ID, in the format of 'INV#{createdBy.id}#{unixTimestampUUID(createdAt)}'" + "(Immutable) Invoice ID, in the format of 'INV#{createdBy.id}#{UUID}'" id: ID! - "(Immutable) The FixitUser who created/sent the Invoice" - createdBy: FixitUser! - "(Immutable) The FixitUser to whom the Invoice was assigned, AKA the Invoice's recipient" - assignedTo: FixitUser! + "(Immutable) The User who created/sent the Invoice" + createdBy: User! + "(Immutable) The User to whom the Invoice was assigned, AKA the Invoice's recipient" + assignedTo: User! "The Invoice amount, represented as an integer which reflects USD centage (i.e., an 'amount' of 1 = $0.01 USD)" amount: Int! "The Invoice status; this field is controlled by the API and can not be directly edited by Users" @@ -33,12 +31,12 @@ export const typeDefs = gql` extend type Query { invoice(invoiceID: ID!): Invoice! - myInvoices: MyInvoicesQueryReturnType! + myInvoices: MyInvoicesQueryResponse! } # QUERY RETURN TYPES - type MyInvoicesQueryReturnType { + type MyInvoicesQueryResponse { createdByUser: [Invoice!]! assignedToUser: [Invoice!]! } diff --git a/src/graphql/Location/helpers.ts b/src/graphql/Location/helpers.ts new file mode 100644 index 00000000..693e8938 --- /dev/null +++ b/src/graphql/Location/helpers.ts @@ -0,0 +1,28 @@ +import { sanitizeName, isValidName } from "@nerdware/ts-string-helpers"; +import { z as zod } from "zod"; +import { sanitizeStreetAddress, isValidStreetAddress } from "@/models/Location/helpers.js"; +import type { CreateLocationInput } from "@/types/graphql.js"; +import type { ZodObjectWithShape } from "@/types/zod.js"; + +/** + * Zod schema for {@link CreateLocationInput} objects. + */ +export const locationInputZodSchema = zod + .object({ + city: zod.string().transform(sanitizeName).refine(isValidName), + country: zod + .string() + .nullish() + .default("USA") + .transform((value) => (value ? sanitizeName(value) : "USA")) + .refine(isValidName), + region: zod.string().transform(sanitizeName).refine(isValidName), + streetLine1: zod.string().transform(sanitizeStreetAddress).refine(isValidStreetAddress), + streetLine2: zod + .string() + .nullish() + .default(null) + .transform((value) => (value ? sanitizeStreetAddress(value) : null)) + .refine((value) => (value ? isValidStreetAddress(value) : value === null)), + }) + .strict() satisfies ZodObjectWithShape; diff --git a/src/graphql/Location/typeDefs.ts b/src/graphql/Location/typeDefs.ts index bc106abb..70285bc4 100644 --- a/src/graphql/Location/typeDefs.ts +++ b/src/graphql/Location/typeDefs.ts @@ -1,6 +1,4 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` +export const typeDefs = `#graphql type Location { streetLine1: String! streetLine2: String diff --git a/src/graphql/Profile/helpers.ts b/src/graphql/Profile/helpers.ts new file mode 100644 index 00000000..34d081b9 --- /dev/null +++ b/src/graphql/Profile/helpers.ts @@ -0,0 +1,46 @@ +import { sanitizeName, isValidName } from "@nerdware/ts-string-helpers"; +import { z as zod } from "zod"; +import { sanitizeDisplayName, isValidDisplayName } from "@/models/Profile/helpers.js"; +import type { ProfileInput } from "@/types/graphql.js"; +import type { ZodObjectWithShape } from "@/types/zod.js"; + +/** + * Zod schema for {@link ProfileInput} objects. + */ +export const createProfileZodSchema = zod + .object({ + displayName: zod + .string() + .nullish() + .default(null) + .transform((value) => (value ? sanitizeDisplayName(value) : null)) + .refine((value) => (value ? isValidDisplayName(value) : value === null), { + message: "Invalid display name", + }), + givenName: zod + .string() + .nullish() + .default(null) + .transform((value) => (value ? sanitizeName(value) : null)) + .refine((value) => (value ? isValidName(value) : value === null), { + message: "Invalid given name", + }), + familyName: zod + .string() + .nullish() + .default(null) + .transform((value) => (value ? sanitizeName(value) : null)) + .refine((value) => (value ? isValidName(value) : value === null), { + message: "Invalid family name", + }), + businessName: zod + .string() + .nullish() + .default(null) + .transform((value) => (value ? sanitizeName(value) : null)) + .refine((value) => (value ? isValidName(value) : value === null), { + message: "Invalid business name", + }), + photoUrl: zod.string().url().nullish().default(null), + }) + .strict() satisfies ZodObjectWithShape; diff --git a/src/graphql/Profile/resolvers.ts b/src/graphql/Profile/resolvers.ts index 77ee9b53..4c75e646 100644 --- a/src/graphql/Profile/resolvers.ts +++ b/src/graphql/Profile/resolvers.ts @@ -1,8 +1,9 @@ -import { Profile } from "@/models/Profile/Profile.js"; -import { User } from "@/models/User/User.js"; +import { Profile } from "@/models/Profile"; +import { User } from "@/models/User"; +import { createProfileZodSchema } from "./helpers.js"; import type { Resolvers } from "@/types/graphql.js"; -export const resolvers: Partial = { +export const resolvers: Resolvers = { Query: { myProfile: async (_parent, _args, { user }) => { const result = await User.getItem({ id: user.id }); @@ -12,8 +13,11 @@ export const resolvers: Partial = { }, Mutation: { updateProfile: async (_parent, { profile: profileInput }, { user }) => { + // Sanitize and validate the provided profileInput + profileInput = createProfileZodSchema.parse(profileInput); + const result = await User.updateItem( - { id: user.id, sk: User.getFormattedSK(user.id) }, + { id: user.id }, { update: { profile: Profile.fromParams(profileInput), @@ -23,7 +27,7 @@ export const resolvers: Partial = { return { ...user.profile, - ...(result?.profile ?? {}), + ...result.profile, }; }, }, diff --git a/src/graphql/Profile/typeDefs.ts b/src/graphql/Profile/typeDefs.ts index 5f497bc2..6d7db58d 100644 --- a/src/graphql/Profile/typeDefs.ts +++ b/src/graphql/Profile/typeDefs.ts @@ -1,6 +1,4 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` +export const typeDefs = `#graphql type Profile { displayName: String! givenName: String diff --git a/src/graphql/FixitUser/resolvers.ts b/src/graphql/PublicUserFields/resolvers.ts similarity index 50% rename from src/graphql/FixitUser/resolvers.ts rename to src/graphql/PublicUserFields/resolvers.ts index dfed7222..c2e8b943 100644 --- a/src/graphql/FixitUser/resolvers.ts +++ b/src/graphql/PublicUserFields/resolvers.ts @@ -1,11 +1,12 @@ +import { CONTACT_SK_PREFIX_STR } from "@/models/Contact"; import type { Resolvers } from "@/types/graphql.js"; -export const resolvers: Partial = { - FixitUser: { +export const resolvers: Resolvers = { + PublicUserFields: { __resolveType: (obj): "User" | "Contact" => { return "__typename" in obj - ? (obj.__typename as "User" | "Contact") - : "id" in obj && obj.id.startsWith("CONTACT#") + ? obj.__typename + : obj.id.startsWith(CONTACT_SK_PREFIX_STR) ? "Contact" : "User"; }, diff --git a/src/graphql/PublicUserFields/typeDefs.ts b/src/graphql/PublicUserFields/typeDefs.ts new file mode 100644 index 00000000..f859adb7 --- /dev/null +++ b/src/graphql/PublicUserFields/typeDefs.ts @@ -0,0 +1,19 @@ +export const typeDefs = `#graphql + """ + PublicUserFields is an interface which defines publicly-accessible User fields. + """ + interface PublicUserFields { + "User or Contact ID" + id: ID! + "Public-facing handle identifies users to other users (e.g., '@joe')" + handle: String! + "User email address" + email: Email! + "User phone number" + phone: String + "User Profile object" + profile: Profile! + createdAt: DateTime! + updatedAt: DateTime! + } +`; diff --git a/src/graphql/PublicUserFields/types.ts b/src/graphql/PublicUserFields/types.ts new file mode 100644 index 00000000..7bff5eff --- /dev/null +++ b/src/graphql/PublicUserFields/types.ts @@ -0,0 +1,10 @@ +import type { User, Contact } from "@/types/graphql.js"; + +/** + * This type is only for use in the `codegen` config file (see `/codegen.ts`) + * + * This type is only used in the `codegen` config file under the `"mappers"` config to ensure + * the `@graphql-codegen/typescript-resolvers` pkg resolves the `PublicUserFields` interface to + * the GQL schema types, `User | Contact`, rather than the DB-model types `UserItem | ContactItem`. + */ +export type PublicUserFieldsCodegenInterface = User | Contact; diff --git a/src/graphql/User/resolvers.ts b/src/graphql/User/resolvers.ts index 7dd1004a..705e71e6 100644 --- a/src/graphql/User/resolvers.ts +++ b/src/graphql/User/resolvers.ts @@ -1,38 +1,37 @@ +import { sanitizeHandle, isValidHandle } from "@nerdware/ts-string-helpers"; +import { isSafeInteger } from "@nerdware/ts-type-safety-utils"; import { usersCache, type UsersCacheEntry } from "@/lib/cache/usersCache.js"; -import { User } from "@/models/User/User.js"; -import { GqlUserInputError, GqlInternalServerError } from "@/utils/httpErrors.js"; -import type { ContactItem } from "@/models/Contact/Contact.js"; +import { UserService } from "@/services/UserService"; +import { UserInputError } from "@/utils/httpErrors.js"; +import type { ContactItem } from "@/models/Contact"; import type { Resolvers } from "@/types/graphql.js"; -export const resolvers: Partial = { +export const resolvers: Resolvers = { Query: { - user: async (_parent, _args, { user }) => { - const result = await User.getItem({ id: user.id }); + getUserByHandle: async (_parent, { handle }) => { + // Sanitize and validate the provided handle + handle = sanitizeHandle(handle); + if (!isValidHandle(handle)) throw new UserInputError(`Invalid value for field: "handle"`); - if (!result) throw new GqlInternalServerError("User not found."); - - return result; - }, - getUserByHandle: (_parent, { handle }, { user: authenticatedUser }) => { - const cachedUser = usersCache.get(handle); - - if (!cachedUser) throw new GqlUserInputError("User not found."); - - return { - ...cachedUser, - userID: authenticatedUser.id, - contactUserID: cachedUser.id, - }; + return await UserService.getUserByHandle({ handle }); }, searchForUsersByHandle: ( _parent, { handle: handleArg, limit, offset: startIndex }, { user: authenticatedUser } ) => { - limit = Math.max(10, Math.min(limit ?? 10, 50)); // limit must be between 10 and 50 - startIndex = Math.max(0, startIndex ?? 0); // startIndex must be >= 0 - - const userCacheEntriesToSearch = usersCache.entries()?.slice(startIndex) ?? []; + // Sanitize and validate the provided handle, limit, and offset + handleArg = sanitizeHandle(handleArg); + if (!isValidHandle(handleArg)) throw new UserInputError(`Invalid value for field: "handle"`); + if (!isSafeInteger(limit) || limit < 0) + throw new UserInputError(`Invalid value for field: "limit"`); + if (!isSafeInteger(startIndex) || startIndex < 0) + throw new UserInputError(`Invalid value for field: "offset"`); + + limit = Math.max(10, Math.min(limit, 50)); // limit must be between 10 and 50 (default: 10) + startIndex = Math.max(0, startIndex); // startIndex must be >= 0 (default: 0) + + const userCacheEntriesToSearch = usersCache.entries().slice(startIndex); const matchingUsers: Array = []; // Why not use reduce? Because we want to break out of the loop once we've found enough matches. diff --git a/src/graphql/User/typeDefs.ts b/src/graphql/User/typeDefs.ts index 6f8e05e3..537cb3fa 100644 --- a/src/graphql/User/typeDefs.ts +++ b/src/graphql/User/typeDefs.ts @@ -1,51 +1,18 @@ -import { gql } from "graphql-tag"; - -/* - IDEA Refactor User.stripeCustomerID into a `stripeCustomer` object with below properties - - id - - defaultPaymentMethodID <-- simple structure - OR - defaultPaymentMethod: { id } <-- would allow other paymentMethod props; requires new type PaymentMethod - OR - paymentMethods: [ { id, isDefault } ] <-- keep data on multiple paymentMethods; requires new type PaymentMethod - OR - invoiceSettings: { defaultPaymentMethod: { id } } <-- most closely resembles Stripe API; requires new type(s) PaymentMethod AND/OR InvoiceSettings - - REASON: For mutation *payInvoice*, when creating the `paymentIntent` via the Stripe API, the - paying User's payment_method is set to `stripeCustomer.invoice_settings.default_payment_method`, - which is an internal Stripe ID string. - - To obtain this payment_method ID, currently the paying User obj is queried by their Fixit - User ID, then the Stripe API is used to retrieve the FULL Stripe Customer object by the - User's `stripeCustomerID`, since in Fixit DDB, only the Stripe Customer ID is currently - stored. -*/ - -export const typeDefs = gql` +export const typeDefs = `#graphql """ - User is an implementation of the FixitUser interface which includes both the - publicly-accessible FixitUser/Contact fields as well as private fields which - are only accessible by the User who owns the data. + User is an implementation of the PublicUserFields interface which represents an individual User. """ - type User implements FixitUser { + type User implements PublicUserFields { "(Immutable) User ID internally identifies individual User accounts" id: ID! "(Immutable) Public-facing handle identifies users to other users (e.g., '@joe')" handle: String! - "(Immutable) User's own email address" + "User's own email address" email: Email! "User's own phone number" phone: String - "(Mobile-Only) User's Expo push token, used to send push notifications to the User's mobile device" - expoPushToken: String "User's own Profile object" profile: Profile! - "User's Stripe Customer ID (defined and generated by Stripe)" - stripeCustomerID: String! - "User Subscription info" - subscription: UserSubscription - "User Stripe Connect Account info" - stripeConnectAccount: UserStripeConnectAccount "(Immutable) Account creation timestamp" createdAt: DateTime! "Timestamp of the most recent account update" @@ -53,13 +20,12 @@ export const typeDefs = gql` } extend type Query { - user: User! """ This query returns the public fields of a User whose handle exactly matches the provided \`handle\` argument. To search for one or more Users whose handle begins with or fuzzy-matches a provided string, use \`searchForUsersByHandle\`. """ - getUserByHandle(handle: String!): Contact + getUserByHandle(handle: String!): User """ This query returns a paginated list of Users whose handle begins with the provided \`handle\` argument, which can be incomplete but must at least contain two characters: @@ -95,6 +61,6 @@ export const typeDefs = gql` excluded from the results. """ offset: Int = 0 - ): [Contact!]! + ): [User!]! } `; diff --git a/src/graphql/UserStripeConnectAccount/typeDefs.ts b/src/graphql/UserStripeConnectAccount/typeDefs.ts index 02c0d6eb..2940308a 100644 --- a/src/graphql/UserStripeConnectAccount/typeDefs.ts +++ b/src/graphql/UserStripeConnectAccount/typeDefs.ts @@ -1,12 +1,17 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` +export const typeDefs = `#graphql + "A user's Stripe Connect Account details" type UserStripeConnectAccount { + "(Immutable) The UserStripeConnectAccount's unique ID" id: ID! + "Whether the user has submitted all required details to set up their Stripe Connect Account" detailsSubmitted: Boolean! + "Whether the user's Stripe Connect Account is enabled for charges" chargesEnabled: Boolean! + "Whether the user's Stripe Connect Account is enabled for payouts" payoutsEnabled: Boolean! + "(Immutable) UserStripeConnectAccount creation timestamp" createdAt: DateTime! + "Timestamp of the most recent UserStripeConnectAccount update" updatedAt: DateTime! } `; diff --git a/src/graphql/UserSubscription/resolvers.ts b/src/graphql/UserSubscription/resolvers.ts index fbabacbf..3377e834 100644 --- a/src/graphql/UserSubscription/resolvers.ts +++ b/src/graphql/UserSubscription/resolvers.ts @@ -1,59 +1,12 @@ -import { - UserSubscription, - type UserSubscriptionItem, -} from "@/models/UserSubscription/UserSubscription.js"; +import { UserSubscriptionService } from "@/services/UserSubscriptionService"; import type { Resolvers } from "@/types/graphql.js"; -export const resolvers: Partial = { +export const resolvers: Resolvers = { Query: { - mySubscription: async (_parent, _args, { user }) => { - /* - Although unlikely, it is possible for users to have multiple subs. - To guard against these edge cases, this query returns the most recently - created subscription with an "active" status. See below comment. - - currentSub win conditions in order of precedence: - - currentSub is 1st in subs array - - currentSub.status is "active" and subToReturn is NOT "active" - - currentSub.status is "active" and created more recently than subToReturn - - neither are "active", and currentSub was updated more recently than subToReturn - */ - return ( - await UserSubscription.query({ - where: { - userID: user.id, - sk: { beginsWith: UserSubscription.SK_PREFIX }, - }, - }) - ).reduce((subToReturn, currentSub) => { - if (!subToReturn) { - subToReturn = currentSub; - } else if ( - currentSub.status === "active" && - (subToReturn.status !== "active" || wasCreatedEarlier(currentSub, subToReturn)) - ) { - subToReturn = currentSub; - } else if (subToReturn.status !== "active" && wasUpdatedLater(currentSub, subToReturn)) { - subToReturn = currentSub; - } - return subToReturn; + mySubscription: async (parent, args, { user }) => { + return await UserSubscriptionService.findUsersSubscription({ + authenticatedUserID: user.id, }); }, }, }; - -// Below: quick utils for making above boolean expressions a little easier to read - -const wasCreatedEarlier = ( - { createdAt: createdAt_1 }: UserSubscriptionItem, - { createdAt: createdAt_2 }: UserSubscriptionItem -) => { - return createdAt_1 < createdAt_2; -}; - -const wasUpdatedLater = ( - { updatedAt: updatedAt_1 }: UserSubscriptionItem, - { updatedAt: updatedAt_2 }: UserSubscriptionItem -) => { - return updatedAt_1 > updatedAt_2; -}; diff --git a/src/graphql/UserSubscription/typeDefs.ts b/src/graphql/UserSubscription/typeDefs.ts index 9d3cf8ce..bd2e5a6e 100644 --- a/src/graphql/UserSubscription/typeDefs.ts +++ b/src/graphql/UserSubscription/typeDefs.ts @@ -1,29 +1,50 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` +export const typeDefs = `#graphql + "A user's subscription to a Fixit SaaS product" type UserSubscription { + "(Immutable) The UserSubscription's unique ID" id: ID! + "The timestamp indicating when the current UserSubscription period ends" currentPeriodEnd: DateTime! + "The UserSubscription's product ID, as provided by Stripe" productID: String! + "The UserSubscription's price ID, as provided by Stripe" priceID: String! + "The UserSubscription's status, as provided by Stripe" status: SubscriptionStatus! + "(Immutable) UserSubscription creation timestamp" createdAt: DateTime! + "Timestamp of the most recent UserSubscription update" updatedAt: DateTime! } + """ + The status of a User's Subscription, as provided by Stripe. + See https://docs.stripe.com/api/subscriptions/object#subscription_object-status + """ enum SubscriptionStatus { + "The subscription is active and the user has access to the product" active + "The subscription is incomplete and has not yet expired" incomplete + "The subscription is incomplete and has expired" incomplete_expired + "The subscription is in a limited trial phase and has access to the product" trialing + "The subscription is past due and the user has lost access to the product" past_due + "The subscription is canceled and the user has lost access to the product" canceled + "The subscription is unpaid and the user has lost access to the product" unpaid } - enum SubscriptionPriceLabel { + "Names of the currently available Fixit subscription price-models" + enum SubscriptionPriceName { + "The annual subscription price model" ANNUAL + "The monthly subscription price model" MONTHLY + "The trial subscription price model" TRIAL } diff --git a/src/graphql/WorkOrder/helpers.ts b/src/graphql/WorkOrder/helpers.ts new file mode 100644 index 00000000..79fecb24 --- /dev/null +++ b/src/graphql/WorkOrder/helpers.ts @@ -0,0 +1,85 @@ +import { + sanitizeAlphabetic, + sanitizePhone, + isValidPhone, + sanitizeName, + isValidName, + sanitizeText, + isValidText, +} from "@nerdware/ts-string-helpers"; +import { z as zod } from "zod"; +import { + createChecklistItemZodSchema, + updateChecklistItemZodSchema, +} from "@/graphql/Checklist/helpers.js"; +import { locationInputZodSchema } from "@/graphql/Location/helpers.js"; +import { userModelHelpers } from "@/models/User/helpers.js"; +import { WORK_ORDER_ENUM_CONSTANTS as WO_ENUMS } from "@/models/WorkOrder/enumConstants.js"; +import type { CreateWorkOrderInput, UpdateWorkOrderInput } from "@/types/graphql.js"; +import type { ZodObjectWithShape } from "@/types/zod.js"; + +/** + * Zod schema for {@link CreateWorkOrderInput} objects. + */ +export const createWorkOrderZodSchema = zod + .object({ + assignedTo: zod + .string() + .nullish() + .default(null) + .transform((value) => (value ? userModelHelpers.id.sanitize(value) : null)) + .refine((value) => (value ? userModelHelpers.id.validate(value) : value === null), { + message: `Invalid user ID for field "assignedTo".`, + }), + category: zod + .enum(WO_ENUMS.CATEGORIES) + .nullish() + .default(null) + .transform((value) => (value ? (sanitizeAlphabetic(value) as typeof value) : value)) + .refine((value) => (value ? WO_ENUMS.CATEGORIES.includes(value) : true)), + checklist: zod.array(createChecklistItemZodSchema).nullish().default(null), + description: zod + .string() + .nullish() + .default(null) + .transform((value) => (value ? sanitizeText(value) : value)) + .refine((value) => (value ? isValidText(value) : value === null)), + dueDate: zod.date().nullish().default(null), + entryContact: zod + .string() + .nullish() + .default(null) + .transform((value) => (value ? sanitizeName(value) : value)) + .refine((value) => (value ? isValidName(value) : value === null)), + entryContactPhone: zod + .string() + .nullish() + .default(null) + .transform((value) => (value ? sanitizePhone(value) : value)) + .refine((value) => (value ? isValidPhone(value) : value === null)), + location: locationInputZodSchema, + priority: zod + .enum(WO_ENUMS.PRIORITIES) + .nullish() + .default("NORMAL") + .transform((value) => (value ? (sanitizeAlphabetic(value) as typeof value) : value)) + .refine((value) => (value ? WO_ENUMS.PRIORITIES.includes(value) : true)), + scheduledDateTime: zod.date().nullish().default(null), + }) + .strict() satisfies ZodObjectWithShape; + +/** + * Zod schema for {@link UpdateWorkOrderInput} objects. + */ +export const updateWorkOrderZodSchema = createWorkOrderZodSchema.omit({ assignedTo: true }).extend({ + assignedToUserID: zod + .string() + .nullish() + .default(null) + .transform((value) => (value ? userModelHelpers.id.sanitize(value) : null)) + .refine((value) => (value ? userModelHelpers.id.validate(value) : value === null), { + message: `Invalid user ID for field "assignedTo".`, + }), + location: locationInputZodSchema.nullish().default(null), + checklist: zod.array(updateChecklistItemZodSchema).nullish().default(null), +}) satisfies ZodObjectWithShape; diff --git a/src/graphql/WorkOrder/resolvers.ts b/src/graphql/WorkOrder/resolvers.ts index eeadf2a1..26af1571 100644 --- a/src/graphql/WorkOrder/resolvers.ts +++ b/src/graphql/WorkOrder/resolvers.ts @@ -1,232 +1,91 @@ -import { isString } from "@nerdware/ts-type-safety-utils"; -import { eventEmitter } from "@/events/eventEmitter.js"; -import { DeleteMutationResponse } from "@/graphql/_common"; -import { - verifyUserIsAuthorizedToPerformThisUpdate, - formatAsGqlFixitUser, -} from "@/graphql/_helpers"; -import { Location } from "@/models/Location/Location.js"; -import { USER_ID_REGEX } from "@/models/User/regex.js"; -import { WorkOrder, type WorkOrderItem } from "@/models/WorkOrder/WorkOrder.js"; -import { GqlUserInputError } from "@/utils/httpErrors.js"; +import { hasKey } from "@nerdware/ts-type-safety-utils"; +import { DeleteMutationResponse } from "@/graphql/_responses/index.js"; +import { User } from "@/models/User"; +import { WORK_ORDER_STATUSES as WO_STATUSES } from "@/models/WorkOrder/enumConstants.js"; +import { workOrderModelHelpers } from "@/models/WorkOrder/helpers.js"; +import { WorkOrderService } from "@/services/WorkOrderService"; +import { createWorkOrderZodSchema, updateWorkOrderZodSchema } from "./helpers.js"; import type { Resolvers } from "@/types/graphql.js"; -export const resolvers: Partial = { +export const resolvers: Resolvers = { Query: { workOrder: async (_parent, { workOrderID }) => { - const [existingWO] = await WorkOrder.query({ - where: { id: workOrderID }, - limit: 1, - }); - - assertWorkOrderWasFound(existingWO); + // Sanitize workOrderID + workOrderID = workOrderModelHelpers.id.sanitizeAndValidate(workOrderID); - return existingWO; + return await WorkOrderService.findWorkOrderByID({ workOrderID }); }, myWorkOrders: async (_parent, _args, { user }) => { - // Query for all WorkOrders created by the authenticated User - const createdByUserQueryResults = await WorkOrder.query({ - where: { - createdByUserID: user.id, - id: { beginsWith: WorkOrder.SK_PREFIX }, - }, - }); - - // Query for all WorkOrders assigned to the authenticated User - const assignedToUserQueryResults = await WorkOrder.query({ - where: { - assignedToUserID: user.id, - id: { beginsWith: WorkOrder.SK_PREFIX }, - }, - }); - - // Map each query's results into the GraphQL schema's WorkOrder shape // TODO maybe let resolvers do this? - return { - createdByUser: await Promise.all( - createdByUserQueryResults.map(async (woCreatedByUser) => ({ - ...woCreatedByUser, - createdBy: { ...user }, - assignedTo: !woCreatedByUser?.assignedToUserID - ? null - : await formatAsGqlFixitUser({ id: woCreatedByUser.assignedToUserID }, user), - })) - ), - assignedToUser: await Promise.all( - assignedToUserQueryResults.map(async (woAssignedToUser) => ({ - ...woAssignedToUser, - createdBy: await formatAsGqlFixitUser({ id: woAssignedToUser.createdByUserID }, user), - assignedTo: { ...user }, - })) - ), - }; + return await WorkOrderService.queryUsersWorkOrders({ authenticatedUser: user }); }, }, Mutation: { - createWorkOrder: async (_parent, { workOrder: woInput }, { user }) => { - const { assignedTo = "UNASSIGNED", location, ...createWorkOrderInput } = woInput; + createWorkOrder: async (_parent, { workOrder: createWorkOrderInput }, { user }) => { + // Sanitize and validate the provided createWorkOrderInput + createWorkOrderInput = createWorkOrderZodSchema.parse(createWorkOrderInput); - const createdWO = await WorkOrder.createItem({ + return await WorkOrderService.createWorkOrder({ createdByUserID: user.id, - assignedToUserID: assignedTo, - status: assignedTo === "UNASSIGNED" ? "UNASSIGNED" : "ASSIGNED", - location: Location.fromParams(location), ...createWorkOrderInput, }); - - eventEmitter.emitWorkOrderCreated(createdWO); - - return createdWO; }, updateWorkOrder: async (_parent, { workOrderID, workOrder: woInput }, { user }) => { - const [existingWO] = await WorkOrder.query({ - where: { id: workOrderID }, - limit: 1, - }); - - verifyUserIsAuthorizedToPerformThisUpdate(existingWO, { - itemNotFoundErrorMessage: WORK_ORDER_NOT_FOUND_ERROR_MSG, - idOfUserWhoCanPerformThisUpdate: existingWO?.createdByUserID, + // Sanitize workOrderID + workOrderID = workOrderModelHelpers.id.sanitizeAndValidate(workOrderID); + // Sanitize and validate the provided woInput + woInput = updateWorkOrderZodSchema.parse(woInput); + + return await WorkOrderService.updateWorkOrder({ + workOrderID, + update: woInput, authenticatedUserID: user.id, - forbiddenStatuses: { - CANCELLED: "The requested work order has been cancelled and cannot be updated.", - }, }); - - /* Check if woInput args necessitate updating the STATUS: - - If the existingWO.status is ASSIGNED and woInput.assignedToUserID is UNASSIGNED, - then the status should be updated to UNASSIGNED. - - If the existingWO.status is UNASSIGNED and woInput.assignedToUserID is a valid - User/Contact ID, then the status should be updated to ASSIGNED. - - Otherwise, the status should remain unchanged. */ - const upToDateStatus = - existingWO.status === "ASSIGNED" && woInput?.assignedToUserID === "UNASSIGNED" - ? "UNASSIGNED" - : existingWO.status === "UNASSIGNED" && - isString(woInput?.assignedToUserID) && - USER_ID_REGEX.test(woInput.assignedToUserID.replace(/CONTACT#/, "")) - ? "ASSIGNED" - : existingWO.status; - - /* Extract `location`, and if provided, provide it to Location.fromParams. - Note that `fromParams` will throw if required fields are not present. Since - Location's are stored as compound-string attributes, they can not be partially - updated, i.e., if it's desirable to only change `streetLine1`, it can not be - updated without all the other Location fields being provided as well. */ - const { location, ...woFieldsToUpdate } = woInput; - - const updatedWO = await WorkOrder.updateItem( - { id: existingWO.id, createdByUserID: existingWO.createdByUserID }, - { - update: { - ...woFieldsToUpdate, - ...(!!location && { location: Location.fromParams(location) }), - status: upToDateStatus, - }, - } - ); - - eventEmitter.emitWorkOrderUpdated(updatedWO, existingWO); - - return updatedWO; }, cancelWorkOrder: async (_parent, { workOrderID }, { user }) => { - const [existingWO] = await WorkOrder.query({ - where: { id: workOrderID }, - limit: 1, - }); + // Sanitize workOrderID + workOrderID = workOrderModelHelpers.id.sanitizeAndValidate(workOrderID); - verifyUserIsAuthorizedToPerformThisUpdate(existingWO, { - itemNotFoundErrorMessage: WORK_ORDER_NOT_FOUND_ERROR_MSG, - idOfUserWhoCanPerformThisUpdate: existingWO?.createdByUserID, + const { deleted, workOrder } = await WorkOrderService.cancelWorkOrder({ + workOrderID, authenticatedUserID: user.id, - forbiddenStatuses: { - CANCELLED: "The requested work order has already been cancelled.", - COMPLETE: "The requested work order has already been completed and cannot be cancelled.", - }, }); - /* If status is UNASSIGNED, delete it and return a DeleteMutationResponse. - Otherwise, update WO status to CANCELLED and return the updated WorkOrder.*/ - - if (existingWO.status === "UNASSIGNED") { - await WorkOrder.deleteItem({ createdByUserID: user.id, id: workOrderID }); - return new DeleteMutationResponse({ id: workOrderID, wasDeleted: true }); - } else { - const canceledWO = await WorkOrder.updateItem( - { id: existingWO.id, createdByUserID: existingWO.createdByUserID }, - { - update: { - status: "CANCELLED", - }, - } - ); - - eventEmitter.emitWorkOrderCancelled(canceledWO); - - return canceledWO; - } + return deleted ? new DeleteMutationResponse({ success: true, id: workOrder.id }) : workOrder; }, setWorkOrderStatusComplete: async (_parent, { workOrderID }, { user }) => { - const [existingWO] = await WorkOrder.query({ - where: { id: workOrderID }, - limit: 1, - }); + // Sanitize workOrderID + workOrderID = workOrderModelHelpers.id.sanitizeAndValidate(workOrderID); - verifyUserIsAuthorizedToPerformThisUpdate(existingWO, { - itemNotFoundErrorMessage: WORK_ORDER_NOT_FOUND_ERROR_MSG, - idOfUserWhoCanPerformThisUpdate: existingWO?.assignedToUserID, + return await WorkOrderService.setWorkOrderStatusComplete({ + workOrderID, authenticatedUserID: user.id, - forbiddenStatuses: { - UNASSIGNED: "Only the work order's assignee may perform this update.", - CANCELLED: "The requested work order has been cancelled and cannot be marked complete.", - COMPLETE: "The requested work order has already been marked complete.", - }, }); - - const updatedWO = await WorkOrder.updateItem( - { id: existingWO.id, createdByUserID: existingWO.createdByUserID }, - { - update: { - status: "COMPLETE", - }, - } - ); - - eventEmitter.emitWorkOrderCompleted(updatedWO); - - return updatedWO; }, }, WorkOrder: { - createdBy: async (workOrder, _args, { user }) => { - return await formatAsGqlFixitUser({ id: workOrder.createdByUserID }, user); + createdBy: async ({ createdByUserID }, _args, { user }) => { + if (createdByUserID === user.id) return user; + + const createdByUser = await User.getItem({ id: createdByUserID }); + + return createdByUser!; }, - assignedTo: async (workOrder, _args, { user }) => { - return workOrder?.assignedToUserID - ? await formatAsGqlFixitUser({ id: workOrder.assignedToUserID }, user) - : null; + assignedTo: async ({ assignedToUserID }, _args, { user }) => { + if (!assignedToUserID || assignedToUserID === WO_STATUSES.UNASSIGNED) return null; + if (assignedToUserID === user.id) return user; + + const assignedToUser = await User.getItem({ id: assignedToUserID }); + + return assignedToUser!; }, }, CancelWorkOrderResponse: { __resolveType(parent) { - return "createdBy" in parent + return hasKey(parent, "createdBy") || hasKey(parent, "location") ? "WorkOrder" - : parent.id && "wasDeleted" in parent + : parent.id && hasKey(parent, "success") ? "DeleteMutationResponse" : null; // null --> GraphQLError is thrown }, }, }; - -const WORK_ORDER_NOT_FOUND_ERROR_MSG = "A work order with the provided ID could not be found."; - -/** - * Asserts that a WorkOrder was found in the database. - */ -export function assertWorkOrderWasFound( - workOrder: W | undefined -): asserts workOrder is NonNullable { - if (!workOrder) { - throw new GqlUserInputError(WORK_ORDER_NOT_FOUND_ERROR_MSG); - } -} diff --git a/src/graphql/WorkOrder/typeDefs.ts b/src/graphql/WorkOrder/typeDefs.ts index 8d0baae6..b142b246 100644 --- a/src/graphql/WorkOrder/typeDefs.ts +++ b/src/graphql/WorkOrder/typeDefs.ts @@ -1,22 +1,19 @@ -import { gql } from "graphql-tag"; - -// IDEA Consider renaming field WorkOrder.contractorNotes to WorkOrder.recipientNotes - -export const typeDefs = gql` +export const typeDefs = `#graphql "A WorkOrder is a request one User submits to another for work to be performed at a location" type WorkOrder { - "(Immutable) WorkOrder ID, in the format of 'WO#{createdBy.id}#{unixTimestampUUID(createdAt)}'" + "(Immutable) WorkOrder ID, in the format of 'WO#{createdBy.id}#{UUID}'" id: ID! - "(Immutable) The FixitUser who created/sent the WorkOrder" - createdBy: FixitUser! - "The FixitUser to whom the WorkOrder was assigned, AKA the WorkOrder's recipient" - assignedTo: FixitUser + "(Immutable) The User who created/sent the WorkOrder" + createdBy: User! + "The User to whom the WorkOrder was assigned, AKA the WorkOrder's recipient" + assignedTo: User "The WorkOrder status" status: WorkOrderStatus! "The WorkOrder priority" priority: WorkOrderPriority! "The location of the job site for the WorkOrder" location: Location! + # IDEA Consider changing 'category' to a tag-style system to allow multiple category tags on a WorkOrder. "The category of work to be performed as part of the WorkOrder" category: WorkOrderCategory "A general description of the work to be performed as part of the WorkOrder" @@ -31,6 +28,7 @@ export const typeDefs = gql` entryContactPhone: String "Timestamp of the WorkOrder's scheduled completion" scheduledDateTime: DateTime + # IDEA Consider renaming field WorkOrder.contractorNotes to WorkOrder.recipientNotes. "Notes from the WorkOrder's recipient (this field will be renamed in the future)" contractorNotes: String "(Immutable) WorkOrder creation timestamp" @@ -39,36 +37,63 @@ export const typeDefs = gql` updatedAt: DateTime! } + "The current status of a WorkOrder" enum WorkOrderStatus { + "The WorkOrder has not yet been assigned to a recipient" UNASSIGNED + "The WorkOrder has been assigned to a recipient but has not yet been started" ASSIGNED + "The WorkOrder is in progress" IN_PROGRESS + "The WorkOrder has been deferred to a later date" DEFERRED + "The WorkOrder has been cancelled" CANCELLED + "The WorkOrder has been completed" COMPLETE } + "The general priority of a WorkOrder" enum WorkOrderPriority { + "The WorkOrder is of low priority" LOW + "The WorkOrder is of normal priority" NORMAL + "The WorkOrder is of high priority" HIGH } + "The category of work to be performed as part of a WorkOrder" enum WorkOrderCategory { + "The WorkOrder involves drywall" DRYWALL + "The WorkOrder involves electrical" ELECTRICAL + "The WorkOrder involves flooring" FLOORING + "The WorkOrder involves general maintenance" GENERAL + "The WorkOrder involves HVAC" HVAC + "The WorkOrder involves landscaping" LANDSCAPING + "The WorkOrder involves masonry" MASONRY + "The WorkOrder involves painting" PAINTING + "The WorkOrder involves paving" PAVING + "The WorkOrder involves pest control" PEST + "The WorkOrder involves plumbing" PLUMBING + "The WorkOrder involves roofing" ROOFING + "The WorkOrder involves trash removal" TRASH + "The WorkOrder involves turnover, i.e., general 'make-ready' tasks for a new tenant or owner" TURNOVER + "The WorkOrder involves windows" WINDOWS } @@ -77,12 +102,12 @@ export const typeDefs = gql` extend type Query { workOrder(workOrderID: ID!): WorkOrder! - myWorkOrders: MyWorkOrdersQueryReturnType! + myWorkOrders: MyWorkOrdersQueryResponse! } # QUERY RETURN TYPES - type MyWorkOrdersQueryReturnType { + type MyWorkOrdersQueryResponse { createdByUser: [WorkOrder!]! assignedToUser: [WorkOrder!]! } @@ -99,6 +124,7 @@ export const typeDefs = gql` # MUTATION INPUT TYPES + "Input for creating a new WorkOrder" input CreateWorkOrderInput { assignedTo: ID priority: WorkOrderPriority @@ -112,6 +138,7 @@ export const typeDefs = gql` scheduledDateTime: DateTime } + "Input for updating an existing WorkOrder" input UpdateWorkOrderInput { assignedToUserID: ID priority: WorkOrderPriority @@ -127,6 +154,7 @@ export const typeDefs = gql` # MUTATION RESPONSE TYPES + "Mutation response for setting a WorkOrder's status to CANCELLED" union CancelWorkOrderResponse = WorkOrder | DeleteMutationResponse #################################################################### diff --git a/src/graphql/_common/DeleteMutationResponse/DeleteMutationResponse.ts b/src/graphql/_common/DeleteMutationResponse/DeleteMutationResponse.ts deleted file mode 100644 index ea8357ac..00000000 --- a/src/graphql/_common/DeleteMutationResponse/DeleteMutationResponse.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { DeleteMutationResponse as DeleteMutationResponseType } from "@/types/graphql.js"; - -export class DeleteMutationResponse implements DeleteMutationResponseType { - id: string; - wasDeleted: boolean; - - constructor({ id, wasDeleted }: { id: string; wasDeleted: boolean }) { - this.id = id; - this.wasDeleted = wasDeleted; - } -} diff --git a/src/graphql/_common/DeleteMutationResponse/typeDefs.ts b/src/graphql/_common/DeleteMutationResponse/typeDefs.ts deleted file mode 100644 index 9ca53e52..00000000 --- a/src/graphql/_common/DeleteMutationResponse/typeDefs.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` - type DeleteMutationResponse { - id: ID! - wasDeleted: Boolean! - } -`; diff --git a/src/graphql/_common/GenericSuccessResponse/GenericSuccessResponse.ts b/src/graphql/_common/GenericSuccessResponse/GenericSuccessResponse.ts deleted file mode 100644 index febf0186..00000000 --- a/src/graphql/_common/GenericSuccessResponse/GenericSuccessResponse.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { GenericSuccessResponse as GenericSuccessResponseType } from "@/types/graphql.js"; - -export class GenericSuccessResponse implements GenericSuccessResponseType { - wasSuccessful: boolean; - - constructor({ wasSuccessful }: { wasSuccessful: boolean }) { - this.wasSuccessful = wasSuccessful; - } -} diff --git a/src/graphql/_common/GenericSuccessResponse/index.ts b/src/graphql/_common/GenericSuccessResponse/index.ts deleted file mode 100644 index 47eddec6..00000000 --- a/src/graphql/_common/GenericSuccessResponse/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./GenericSuccessResponse.js"; diff --git a/src/graphql/_common/GenericSuccessResponse/typeDefs.ts b/src/graphql/_common/GenericSuccessResponse/typeDefs.ts deleted file mode 100644 index c5a45072..00000000 --- a/src/graphql/_common/GenericSuccessResponse/typeDefs.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` - """ - Generic response-type for mutations which simply returns a "wasSuccessful" boolean. - This is only ever used as a "last-resort" response-type for mutations which meet all - of the following criteria: - 1. The mutation does not perform any database CRUD operations. - 2. The mutation does not perform any CRUD operations on data maintained by the client-side cache. - 3. No other response-type is appropriate for the mutation. - - Typically the only mutations for which this reponse-type is appropriate are those which - perform some sort of "side-effect" (e.g. sending an email, sending a text message, etc.). - """ - type GenericSuccessResponse { - wasSuccessful: Boolean! - } -`; diff --git a/src/graphql/_customScalars/Email/typeDefs.ts b/src/graphql/_customScalars/Email/typeDefs.ts deleted file mode 100644 index 45027215..00000000 --- a/src/graphql/_customScalars/Email/typeDefs.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` - "Custom Email scalar with regex validation" - scalar Email -`; diff --git a/src/graphql/_customScalars/helpers.ts b/src/graphql/_customScalars/helpers.ts deleted file mode 100644 index d99df55a..00000000 --- a/src/graphql/_customScalars/helpers.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { safeJsonStringify } from "@nerdware/ts-type-safety-utils"; - -/** - * GQL Custom Scalar Helper Methods - */ -export const helpers = { - getScalarErrMsg: (scalarType: string, invalidValue: unknown) => { - return ( - `[${scalarType.toUpperCase()} SCALAR ERROR]: ` + - `Client provided an invalid ${scalarType.toLowerCase()}: ` + - safeJsonStringify(invalidValue) - ); - }, -}; diff --git a/src/graphql/_helpers/formatAsGqlFixitUser.ts b/src/graphql/_helpers/formatAsGqlFixitUser.ts deleted file mode 100644 index 600ca480..00000000 --- a/src/graphql/_helpers/formatAsGqlFixitUser.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { usersCache } from "@/lib/cache/usersCache.js"; -import { contactModelHelpers } from "@/models/Contact/helpers.js"; -import { User } from "@/models/User/User.js"; -import type { FixitUser } from "@/types/graphql.js"; -import type { FixitApiAuthTokenPayload } from "@/utils/AuthToken.js"; -import type { SetOptional } from "type-fest"; - -/** - * This function converts `Users`/`Contacts` from their internal database shape/format into the - * GraphQL schema's `FixitUser` shape/format. This function is used in the `WorkOrder` and `Invoice` - * resolvers to get the `createdBy`/`assignedTo` fields. - */ -export const formatAsGqlFixitUser = async ( - /** - * The `User` or `Contact` data to format as a GQLFixitUser object. Note that the `handle` field - * is optional since it isn't present on relational fields like the `createdBy` and `assignedTo` - * fields of `WorkOrder` and `Invoice` items. - */ - { - id: itemUserID, - handle: itemUserHandle, - }: SetOptional, "handle">, - /** - * The currently authenticated User's AuthToken is used to determine if the data in the first - * param belongs to the currently authenticated User or not. - */ - userAuthToken: FixitApiAuthTokenPayload -): Promise => { - let fixitUser: (FixitUser & Record) | undefined; - - // If auth'd user is the FixitUser, use authToken fields - if (itemUserID === userAuthToken.id) { - fixitUser = { - __typename: "User", - id: userAuthToken.id, - handle: userAuthToken.handle, - email: userAuthToken.email, - phone: userAuthToken.phone ?? null, - profile: userAuthToken.profile, - createdAt: userAuthToken.createdAt, - updatedAt: userAuthToken.updatedAt, - }; - } else if (itemUserHandle) { - // Else check usersCache if an itemUserHandle was provided - const maybeUser = usersCache.get(itemUserHandle); - - if (maybeUser) { - // Apply the data from the cache, formatting the UserID as a ContactID - const { id: contactUserID, ...contactFields } = maybeUser; - fixitUser = { - __typename: "Contact", - ...contactFields, - id: contactModelHelpers.id.format(contactUserID), - }; - } - } - - // If neither of the above methods yielded data for FixitUser, get it from the DB as a last resort. - if (!fixitUser) { - const maybeUser = await User.getItem({ id: itemUserID }); - - if (maybeUser) { - fixitUser = { - __typename: "Contact", - ...maybeUser, - id: contactModelHelpers.id.format(itemUserID), - }; - } - } - - // If for some reason there's still no data for FixitUser, throw an error. - if (!fixitUser) throw new Error("User not found."); - - return fixitUser; -}; diff --git a/src/graphql/_helpers/index.ts b/src/graphql/_helpers/index.ts deleted file mode 100644 index 2ac7d080..00000000 --- a/src/graphql/_helpers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./formatAsGqlFixitUser.js"; -export * from "./verifyUserIsAuthorizedToPerformThisUpdate.js"; diff --git a/src/graphql/_responses/DeleteMutationResponse/DeleteMutationResponse.ts b/src/graphql/_responses/DeleteMutationResponse/DeleteMutationResponse.ts new file mode 100644 index 00000000..3ed17bc3 --- /dev/null +++ b/src/graphql/_responses/DeleteMutationResponse/DeleteMutationResponse.ts @@ -0,0 +1,24 @@ +import { + MutationResponse, + type MutationResponseParams, +} from "@/graphql/_responses/MutationResponse"; +import type { DeleteMutationResponse as DeleteMutationResponseType } from "@/types/graphql.js"; + +/** + * A mutation response class for delete operations. + */ +export class DeleteMutationResponse extends MutationResponse implements DeleteMutationResponseType { + id: string; + + constructor({ success, id }: DeleteMutationResponseParams) { + super({ success }); + this.id = id; + } +} + +/** + * Constructor params for a {@link DeleteMutationResponse}. + */ +export type DeleteMutationResponseParams = MutationResponseParams & { + id: string; +}; diff --git a/src/graphql/_common/DeleteMutationResponse/index.ts b/src/graphql/_responses/DeleteMutationResponse/index.ts similarity index 100% rename from src/graphql/_common/DeleteMutationResponse/index.ts rename to src/graphql/_responses/DeleteMutationResponse/index.ts diff --git a/src/graphql/_responses/DeleteMutationResponse/typeDefs.ts b/src/graphql/_responses/DeleteMutationResponse/typeDefs.ts new file mode 100644 index 00000000..8f07c412 --- /dev/null +++ b/src/graphql/_responses/DeleteMutationResponse/typeDefs.ts @@ -0,0 +1,10 @@ +export const typeDefs = `#graphql + "A mutation response type for delete operations." + type DeleteMutationResponse implements MutationResponse { + success: Boolean! + code: String + message: String + "The ID of the deleted entity." + id: ID! + } +`; diff --git a/src/graphql/_responses/MutationResponse/MutationResponse.ts b/src/graphql/_responses/MutationResponse/MutationResponse.ts new file mode 100644 index 00000000..8dfa4101 --- /dev/null +++ b/src/graphql/_responses/MutationResponse/MutationResponse.ts @@ -0,0 +1,19 @@ +import type { MutationResponse as MutationResponseType } from "@/types/graphql.js"; + +/** + * A generic mutation response class. + */ +export class MutationResponse implements MutationResponseType { + success: boolean; + + constructor({ success }: MutationResponseParams) { + this.success = success; + } +} + +/** + * Constructor params for a {@link MutationResponse}. + */ +export type MutationResponseParams = { + success: boolean; +}; diff --git a/src/graphql/_responses/MutationResponse/index.ts b/src/graphql/_responses/MutationResponse/index.ts new file mode 100644 index 00000000..98ff36b3 --- /dev/null +++ b/src/graphql/_responses/MutationResponse/index.ts @@ -0,0 +1 @@ +export * from "./MutationResponse.js"; diff --git a/src/graphql/_responses/MutationResponse/typeDefs.ts b/src/graphql/_responses/MutationResponse/typeDefs.ts new file mode 100644 index 00000000..03d14916 --- /dev/null +++ b/src/graphql/_responses/MutationResponse/typeDefs.ts @@ -0,0 +1,15 @@ +export const typeDefs = `#graphql + "A generic mutation response type." + interface MutationResponse { + "Whether the mutation was successful." + success: Boolean! + """ + A code for the mutation response. This may be an HTTP status code, like '200', + or a GraphQLError code, like 'BAD_USER_INPUT', depending on what makes sense + for the implementing type. + """ + code: String + "An optional message to provide additional info about the mutation response." + message: String + } +`; diff --git a/src/graphql/_common/index.ts b/src/graphql/_responses/index.ts similarity index 50% rename from src/graphql/_common/index.ts rename to src/graphql/_responses/index.ts index a920497a..1b9643ee 100644 --- a/src/graphql/_common/index.ts +++ b/src/graphql/_responses/index.ts @@ -1,2 +1,2 @@ +export * from "./MutationResponse"; export * from "./DeleteMutationResponse"; -export * from "./GenericSuccessResponse"; diff --git a/src/graphql/_root/typeDefs.ts b/src/graphql/_root/typeDefs.ts index ac7fd2a4..492dd111 100644 --- a/src/graphql/_root/typeDefs.ts +++ b/src/graphql/_root/typeDefs.ts @@ -1,11 +1,4 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` - type Query { - _root: Boolean - } - - type Mutation { - _root: Boolean - } +export const typeDefs = `#graphql + type Query + type Mutation `; diff --git a/src/graphql/_customScalars/DateTime/resolvers.ts b/src/graphql/_scalars/DateTime/resolvers.ts similarity index 71% rename from src/graphql/_customScalars/DateTime/resolvers.ts rename to src/graphql/_scalars/DateTime/resolvers.ts index d48a5cc5..1add00da 100644 --- a/src/graphql/_customScalars/DateTime/resolvers.ts +++ b/src/graphql/_scalars/DateTime/resolvers.ts @@ -1,9 +1,10 @@ import { GraphQLScalarType, Kind } from "graphql"; import { logger } from "@/utils/logger.js"; import { isValidTimestamp } from "@/utils/timestamps.js"; -import { helpers } from "../helpers.js"; +import { getScalarErrMsg } from "../helpers.js"; +import type { Resolvers } from "@/types/graphql.js"; -export const resolvers = { +export const resolvers: Resolvers = { DateTime: new GraphQLScalarType({ name: "DateTime", description: "Custom DateTime scalar with handling for Date objects and datetime strings", @@ -11,7 +12,7 @@ export const resolvers = { // parseValue = value from the client parseValue(value: unknown) { if (!isValidTimestamp(value)) { - const errMsg = helpers.getScalarErrMsg("DateTime", value); + const errMsg = getScalarErrMsg("DateTime", value); logger.gql(errMsg); throw new TypeError(errMsg); } @@ -21,7 +22,7 @@ export const resolvers = { // serialize = value sent to the client serialize(value: unknown) { if (!isValidTimestamp(value)) { - const errMsg = helpers.getScalarErrMsg("DateTime", value); + const errMsg = getScalarErrMsg("DateTime", value); logger.gql(errMsg); throw new TypeError(errMsg); } @@ -30,10 +31,7 @@ export const resolvers = { // ast value is always in string format parseLiteral(ast) { - if (ast.kind === Kind.INT) { - return new Date(ast.value); - } - return null; + return ast.kind === Kind.INT ? new Date(ast.value) : null; }, }), }; diff --git a/src/graphql/_customScalars/DateTime/typeDefs.ts b/src/graphql/_scalars/DateTime/typeDefs.ts similarity index 60% rename from src/graphql/_customScalars/DateTime/typeDefs.ts rename to src/graphql/_scalars/DateTime/typeDefs.ts index d5d5dfd4..43ccb226 100644 --- a/src/graphql/_customScalars/DateTime/typeDefs.ts +++ b/src/graphql/_scalars/DateTime/typeDefs.ts @@ -1,6 +1,4 @@ -import { gql } from "graphql-tag"; - -export const typeDefs = gql` +export const typeDefs = `#graphql "Custom DateTime scalar with handling for Date objects and datetime strings" scalar DateTime `; diff --git a/src/graphql/_customScalars/Email/resolvers.ts b/src/graphql/_scalars/Email/resolvers.ts similarity index 76% rename from src/graphql/_customScalars/Email/resolvers.ts rename to src/graphql/_scalars/Email/resolvers.ts index 4df4cc65..9a7b6723 100644 --- a/src/graphql/_customScalars/Email/resolvers.ts +++ b/src/graphql/_scalars/Email/resolvers.ts @@ -1,9 +1,10 @@ import { isValidEmail } from "@nerdware/ts-string-helpers"; import { GraphQLScalarType, Kind } from "graphql"; import { logger } from "@/utils/logger.js"; -import { helpers } from "../helpers.js"; +import { getScalarErrMsg } from "../helpers.js"; +import type { Resolvers } from "@/types/graphql.js"; -export const resolvers = { +export const resolvers: Resolvers = { Email: new GraphQLScalarType({ name: "Email", description: "Custom Email scalar with regex validation", @@ -11,7 +12,7 @@ export const resolvers = { // parseValue = value from the client parseValue(value: unknown) { if (!isValidEmail(value)) { - const errMsg = helpers.getScalarErrMsg("Email", value); + const errMsg = getScalarErrMsg("Email", value); logger.gql(errMsg); throw new TypeError(errMsg); } @@ -21,7 +22,7 @@ export const resolvers = { // serialize = value sent to the client serialize(value: unknown) { if (!isValidEmail(value)) { - const errMsg = helpers.getScalarErrMsg("Email", value); + const errMsg = getScalarErrMsg("Email", value); logger.gql(errMsg); throw new TypeError(errMsg); } diff --git a/src/graphql/_scalars/Email/typeDefs.ts b/src/graphql/_scalars/Email/typeDefs.ts new file mode 100644 index 00000000..dc5b529a --- /dev/null +++ b/src/graphql/_scalars/Email/typeDefs.ts @@ -0,0 +1,4 @@ +export const typeDefs = `#graphql + "Custom Email scalar with regex validation" + scalar Email +`; diff --git a/src/graphql/_scalars/helpers.ts b/src/graphql/_scalars/helpers.ts new file mode 100644 index 00000000..1a761ea4 --- /dev/null +++ b/src/graphql/_scalars/helpers.ts @@ -0,0 +1,9 @@ +import { safeJsonStringify } from "@nerdware/ts-type-safety-utils"; + +export const getScalarErrMsg = (scalarType: string, invalidValue: unknown) => { + return ( + `[${scalarType.toUpperCase()} SCALAR ERROR]: ` + + `Client provided an invalid ${scalarType.toLowerCase()}: ` + + safeJsonStringify(invalidValue) + ); +}; diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 0a37f3b8..7f27d763 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -1,72 +1,31 @@ -import { isFunction } from "@nerdware/ts-type-safety-utils"; -import deepMerge from "lodash.merge"; -import { logger } from "@/utils/logger.js"; import * as contact from "./Contact/resolvers.js"; -import * as fixitUser from "./FixitUser/resolvers.js"; +import * as invite from "./Invite/resolvers.js"; import * as invoice from "./Invoice/resolvers.js"; import * as profile from "./Profile/resolvers.js"; +import * as publicUserFields from "./PublicUserFields/resolvers.js"; import * as user from "./User/resolvers.js"; import * as userSubscription from "./UserSubscription/resolvers.js"; import * as workOrder from "./WorkOrder/resolvers.js"; -import * as dateTimeCustomScalar from "./_customScalars/DateTime/resolvers.js"; -import * as emailCustomScalar from "./_customScalars/Email/resolvers.js"; +import * as dateTimeCustomScalar from "./_scalars/DateTime/resolvers.js"; +import * as emailCustomScalar from "./_scalars/Email/resolvers.js"; +import type { Resolvers } from "@/types/graphql.js"; /** - * Fixit API GQL Schema Resolvers - * - Each resolver is wrapped with a try/catch error-logger + * Fixit API — GraphQL Schema Resolvers */ -export const resolvers = Object.fromEntries( - Object.entries( - deepMerge( - // CUSTOM SCALARS - dateTimeCustomScalar.resolvers, - emailCustomScalar.resolvers, - // INTERFACES - fixitUser.resolvers, - // CONCRETE TYPES - contact.resolvers, - invoice.resolvers, - profile.resolvers, - user.resolvers, - userSubscription.resolvers, - workOrder.resolvers - ) - ).map(([resolverType, resolversOfType]) => { - // resolverType is either "Query" or "Mutation" - return [ - resolverType, - Object.fromEntries( - Object.entries(resolversOfType as Record).map(([resolverName, resolver]) => { - // For scalar resolvers and whatnot, "resolver" won't be a function, just continue to next. - if (!isFunction(resolver)) return [resolverName, resolver]; - - // Resolver ID example: "QUERY:WorkOrder" - const resolverLogID = `${resolverType.toUpperCase()}:${resolverName}`; - - /* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ - - /* Note: none of the sync resolver functions use `return new Promise...`, so this - logic using the resolver's constructor.name property works for this use case. */ - const wrappedResolverFn = - resolver.constructor.name === "AsyncFunction" - ? async (...args: unknown[]) => { - return await resolver(...args).catch((err: unknown) => { - logger.error(err, resolverLogID); // Log error and re-throw as-is - throw err; - }); - } - : (...args: unknown[]) => { - try { - return resolver(...args); - } catch (err: unknown) { - logger.error(err, resolverLogID); // Log error and re-throw as-is - throw err; - } - }; - - return [resolverName, wrappedResolverFn]; - }) - ), - ]; - }) -); +export const resolvers: Array = [ + // CUSTOM SCALARS + dateTimeCustomScalar.resolvers, + emailCustomScalar.resolvers, + // INTERFACES + publicUserFields.resolvers, + // CONCRETE TYPES + contact.resolvers, + invoice.resolvers, + profile.resolvers, + user.resolvers, + userSubscription.resolvers, + workOrder.resolvers, + // OTHER TYPES + invite.resolvers, +]; diff --git a/src/graphql/schema.ts b/src/graphql/schema.ts index 5f75b4a8..9a468ae7 100644 --- a/src/graphql/schema.ts +++ b/src/graphql/schema.ts @@ -2,7 +2,10 @@ import { makeExecutableSchema } from "@graphql-tools/schema"; import { resolvers } from "./resolvers.js"; import { typeDefs } from "./typeDefs.js"; -export const fixitSchema = makeExecutableSchema({ +/** + * Fixit API GraphQL Schema + */ +export const schema = makeExecutableSchema({ typeDefs, resolvers, }); diff --git a/src/graphql/typeDefs.ts b/src/graphql/typeDefs.ts index cd1e7b6e..dca6328d 100644 --- a/src/graphql/typeDefs.ts +++ b/src/graphql/typeDefs.ts @@ -1,35 +1,37 @@ -import * as authToken from "./AuthToken/typeDefs.js"; import * as checklist from "./Checklist/typeDefs.js"; import * as contact from "./Contact/typeDefs.js"; -import * as fixitUser from "./FixitUser/typeDefs.js"; +import * as graphQLErrorExtensions from "./GraphQLError/typeDefs.js"; import * as invite from "./Invite/typeDefs.js"; import * as invoice from "./Invoice/typeDefs.js"; import * as location from "./Location/typeDefs.js"; import * as profile from "./Profile/typeDefs.js"; +import * as publicUserFields from "./PublicUserFields/typeDefs.js"; import * as user from "./User/typeDefs.js"; import * as userStripeConnectAccount from "./UserStripeConnectAccount/typeDefs.js"; import * as userSubscription from "./UserSubscription/typeDefs.js"; import * as workOrder from "./WorkOrder/typeDefs.js"; -import * as deleteMutationResponse from "./_common/DeleteMutationResponse/typeDefs.js"; -import * as genericSuccessResponse from "./_common/GenericSuccessResponse/typeDefs.js"; -import * as dateTimeCustomScalar from "./_customScalars/DateTime/typeDefs.js"; -import * as emailCustomScalar from "./_customScalars/Email/typeDefs.js"; +import * as deleteMutationResponse from "./_responses/DeleteMutationResponse/typeDefs.js"; +import * as mutationResponse from "./_responses/MutationResponse/typeDefs.js"; import * as root from "./_root/typeDefs.js"; +import * as dateTimeCustomScalar from "./_scalars/DateTime/typeDefs.js"; +import * as emailCustomScalar from "./_scalars/Email/typeDefs.js"; /** - * Fixit API GQL Schema TypeDefs + * Fixit API — GraphQL Schema TypeDefs */ -export const typeDefs = [ - // EXTENDABLE ROOT TYPES +export const typeDefs: Array = [ + // ROOT TYPES root.typeDefs, // CUSTOM SCALARS dateTimeCustomScalar.typeDefs, emailCustomScalar.typeDefs, + // GQL ERROR EXTENSION TYPES + graphQLErrorExtensions.typeDefs, // MUTATION REPONSE TYPES + mutationResponse.typeDefs, deleteMutationResponse.typeDefs, - genericSuccessResponse.typeDefs, // INTERFACES - fixitUser.typeDefs, + publicUserFields.typeDefs, // CONCRETE TYPES checklist.typeDefs, contact.typeDefs, @@ -40,8 +42,6 @@ export const typeDefs = [ user.typeDefs, userSubscription.typeDefs, workOrder.typeDefs, - // SIDE-EFFECT MUTATIONS + // OTHER TYPES invite.typeDefs, - // OTHER TYPES SHARED WITH CLIENT - authToken.typeDefs, ]; diff --git a/src/httpServer.ts b/src/httpServer.ts new file mode 100644 index 00000000..974398ca --- /dev/null +++ b/src/httpServer.ts @@ -0,0 +1,32 @@ +import http, { type Server as HttpServer } from "node:http"; +import { apolloServer } from "@/apolloServer.js"; +import { expressApp } from "@/expressApp.js"; +import type { Asyncify } from "type-fest"; + +/** + * An {@link HttpServer} instance created from the express app. + * + * See https://www.apollographql.com/docs/apollo-server/api/express-middleware#example + */ +export const httpServer = http.createServer(expressApp) as HttpServerWithCustomStart; + +httpServer.start = async (listenOpts = {}, listenCallback) => { + await apolloServer.configurePlugins({ httpServer }); + await apolloServer.start(); + expressApp.setupMiddleware(); + return httpServer.listen(listenOpts, listenCallback); +}; + +export type HttpServerWithCustomStart = HttpServer & { + /** + * A thin wrapper around `httpServer.listen` which executes server-startup logic: + * + * 1. [`await apolloServer.configurePlugins(...)`](./apolloServer.ts) + * 2. [`await apolloServer.start()`][apollo-server-ref] + * 3. [`expressApp.setupMiddleware()`](./expressApp.ts) + * 4. `httpServer.listen(...)` + * + * [apollo-server-ref]: https://www.apollographql.com/docs/apollo-server/api/apollo-server#start + */ + start: Asyncify; +}; diff --git a/src/index.ts b/src/index.ts index 16ece6b7..3fd4c5c9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,8 @@ import "@/server/init.js"; +import { httpServer } from "@/httpServer.js"; import { ENV } from "@/server/env"; import { logger } from "@/utils/logger.js"; -import { expressApp } from "./expressApp.js"; -const server = expressApp.listen(ENV.CONFIG.PORT, () => { +await httpServer.start({ port: ENV.CONFIG.PORT }, () => { logger.server("👂 Server is listening."); }); - -process.on("exit", () => { - logger.server("(PROC EXIT) API Server: closing connections ..."); - server.close(() => logger.server("(PROC EXIT) API Server: connections closed.")); -}); diff --git a/src/lib/cache/Cache.ts b/src/lib/cache/Cache.ts index d34db2ad..a384333d 100644 --- a/src/lib/cache/Cache.ts +++ b/src/lib/cache/Cache.ts @@ -5,12 +5,12 @@ import type { LiteralToPrimitive } from "type-fest"; * explicit type to be specified for the stored data. If no type is provided, * this defaults to `any`. */ -export interface CacheEntry { +export interface CacheEntry> { data: StoredDataType; expiresAt?: number | undefined; } -export class Cache { +export class Cache, CacheKeyType = string> { protected readonly _cache: Map>; constructor(initialEntries: Array<[CacheKeyType, StoredDataType]> = []) { @@ -86,9 +86,11 @@ export class Cache { * @param {CacheKeyType} key - The cache key to use to retrieve the data. * @returns {StoredDataType|undefined} The data stored under the provided key, or undefined if no unexpired data is found. */ - readonly get = (key: CacheKeyType): StoredDataType | undefined => { - const { data, expiresAt } = this._cache.get(key) ?? {}; - if (!this.isExpiredAndDeleted(key, { expiresAt })) return data; + readonly get = ( + key: CacheKeyType | LiteralToPrimitive + ): StoredDataType | undefined => { + const { data, expiresAt } = this._cache.get(key as CacheKeyType) ?? {}; + if (!this.isExpiredAndDeleted(key as CacheKeyType, { expiresAt })) return data; }; /** diff --git a/src/lib/cache/__mocks__/pricesCache.ts b/src/lib/cache/__mocks__/pricesCache.ts new file mode 100644 index 00000000..cc74037c --- /dev/null +++ b/src/lib/cache/__mocks__/pricesCache.ts @@ -0,0 +1,24 @@ +import { Cache } from "@/lib/cache/Cache.js"; +import { mockStripePrice } from "@/lib/stripe/__mocks__/_mockStripePrice.js"; +import { + SUBSCRIPTION_ENUMS, + SUBSCRIPTION_PRICE_NAMES as PRICE_NAMES, +} from "@/models/UserSubscription/enumConstants.js"; +import type { SubscriptionPriceName } from "@/types/graphql.js"; +import type Stripe from "stripe"; + +/** + * ### Mock Prices Cache + */ +export const pricesCache = new Cache( + SUBSCRIPTION_ENUMS.PRICE_NAMES.map((priceName) => [ + priceName, + mockStripePrice({ + id: `price_Test${priceName}`, + nickname: priceName, + ...(priceName === PRICE_NAMES.TRIAL && { + recurring: { trial_period_days: 14 }, + }), + }), + ]) +); diff --git a/src/lib/cache/__mocks__/productsCache.ts b/src/lib/cache/__mocks__/productsCache.ts new file mode 100644 index 00000000..9a789537 --- /dev/null +++ b/src/lib/cache/__mocks__/productsCache.ts @@ -0,0 +1,13 @@ +import { Cache } from "@/lib/cache/Cache.js"; +import { mockStripeProduct } from "@/lib/stripe/__mocks__/_mockStripeProduct.js"; +import { SUBSCRIPTION_PRODUCT_NAMES as PRODUCT_NAMES } from "@/models/UserSubscription/enumConstants.js"; +import type Stripe from "stripe"; + +/** + * API local cache for Stripe `Product` objects, keyed by `product.name`. + * + * > Currently this only contains the `"Fixit Subscription"` Product object. + */ +export const productsCache = new Cache([ + [PRODUCT_NAMES.FIXIT_SUBSCRIPTION, mockStripeProduct({ name: PRODUCT_NAMES.FIXIT_SUBSCRIPTION })], +]); diff --git a/src/lib/cache/__mocks__/usersCache.ts b/src/lib/cache/__mocks__/usersCache.ts new file mode 100644 index 00000000..379b8b42 --- /dev/null +++ b/src/lib/cache/__mocks__/usersCache.ts @@ -0,0 +1,7 @@ +import { Cache } from "@/lib/cache/Cache.js"; +import type { UsersCacheObject } from "@/lib/cache/usersCache.js"; + +/** + * MOCK Users Cache + */ +export const usersCache = new Cache([]); diff --git a/src/lib/cache/index.ts b/src/lib/cache/index.ts deleted file mode 100644 index 71c4b710..00000000 --- a/src/lib/cache/index.ts +++ /dev/null @@ -1 +0,0 @@ -export type { CacheEntry } from "./Cache.js"; diff --git a/src/lib/cache/pricesCache.ts b/src/lib/cache/pricesCache.ts index 301c1b98..a2ad8abd 100644 --- a/src/lib/cache/pricesCache.ts +++ b/src/lib/cache/pricesCache.ts @@ -1,26 +1,29 @@ import { safeJsonStringify } from "@nerdware/ts-type-safety-utils"; +import Stripe from "stripe"; +import { productsCache } from "@/lib/cache/productsCache.js"; import { stripe } from "@/lib/stripe/stripeClient.js"; +import { + SUBSCRIPTION_PRICE_NAMES as PRICE_NAMES, + SUBSCRIPTION_PRODUCT_NAMES as PRODUCT_NAMES, +} from "@/models/UserSubscription/enumConstants.js"; import { InternalServerError } from "@/utils/httpErrors.js"; import { Cache } from "./Cache.js"; -import { FIXIT_SUBSCRIPTION_PRODUCT_NAME, productsCache } from "./productsCache.js"; -import type { OpenApiSchemas } from "@/types/open-api.js"; -import type Stripe from "stripe"; +import type { SubscriptionPriceName } from "@/types/graphql.js"; import type { Entries } from "type-fest"; -/** The names of Fixit Subscription prices: "TRIAL", "MONTHLY", "ANNUAL" */ -type SubscriptionPriceLabels = OpenApiSchemas["SubscriptionPriceName"]; - // Initialize the pricesCache with all active subscription prices: const { data: activeSubscriptionPrices } = await stripe.prices.list({ active: true, - product: productsCache.get(FIXIT_SUBSCRIPTION_PRODUCT_NAME)!.id, + product: productsCache.get(PRODUCT_NAMES.FIXIT_SUBSCRIPTION)!.id, }); // Ensure exactly 2 active subscription prices were returned from Stripe: if ( - activeSubscriptionPrices?.length !== 2 || - !activeSubscriptionPrices.every((price) => ["MONTHLY", "ANNUAL"].includes(`${price.nickname}`)) + activeSubscriptionPrices.length !== 2 || + !activeSubscriptionPrices.every((price) => + ([PRICE_NAMES.ANNUAL, PRICE_NAMES.MONTHLY] as Array).includes(price.nickname ?? "") + ) ) { throw new InternalServerError( "Unable to initialize pricesCache — Stripe did not return expected prices. " + @@ -29,19 +32,23 @@ if ( } const pricesDictionary = activeSubscriptionPrices.reduce( - (accum, priceObject) => { + (accum: Record, priceObject) => { const { nickname: priceName } = priceObject; - accum[`${priceName}` as SubscriptionPriceLabels] = priceObject; + accum[(priceName ?? "") as SubscriptionPriceName] = priceObject; // TRIAL uses the same priceID as MONTHLY: - if (priceName === "MONTHLY") accum.TRIAL = priceObject; + if (priceName === PRICE_NAMES.MONTHLY) accum.TRIAL = priceObject; return accum; }, - { ANNUAL: {}, MONTHLY: {}, TRIAL: {} } as Record + { + ANNUAL: {} as Stripe.Price, + MONTHLY: {} as Stripe.Price, + TRIAL: {} as Stripe.Price, + } ); /** * API local cache for Stripe `Price` objects, keyed by `price.nickname`. */ -export const pricesCache = new Cache( +export const pricesCache = new Cache( Object.entries(pricesDictionary) as Entries ); diff --git a/src/lib/cache/productsCache.ts b/src/lib/cache/productsCache.ts index 4ec1d281..8bf7b835 100644 --- a/src/lib/cache/productsCache.ts +++ b/src/lib/cache/productsCache.ts @@ -1,23 +1,15 @@ import { safeJsonStringify } from "@nerdware/ts-type-safety-utils"; import { stripe } from "@/lib/stripe/stripeClient.js"; +import { SUBSCRIPTION_PRODUCT_NAMES as PRODUCT_NAMES } from "@/models/UserSubscription/enumConstants.js"; import { InternalServerError } from "@/utils/httpErrors.js"; import { Cache } from "./Cache.js"; import type Stripe from "stripe"; -/** - * The `"Fixit Subscription"` Product `"name"`. - */ -export const FIXIT_SUBSCRIPTION_PRODUCT_NAME = - "Fixit Subscription" as const satisfies Stripe.Product["name"]; - -export type ProductsCacheEntry = [typeof FIXIT_SUBSCRIPTION_PRODUCT_NAME, Stripe.Product]; - // Initialize the productsCache with all active products: - const { data: activeProducts } = await stripe.products.list({ active: true }); -const fixitSubscriptionProduct = activeProducts?.find( - (prod) => prod.name === FIXIT_SUBSCRIPTION_PRODUCT_NAME +const fixitSubscriptionProduct = activeProducts.find( + (prod) => prod.name === PRODUCT_NAMES.FIXIT_SUBSCRIPTION ); // Ensure the Fixit Subscription product was returned from Stripe: @@ -30,9 +22,7 @@ if (!fixitSubscriptionProduct) { /** * API local cache for Stripe `Product` objects, keyed by `product.name`. - * - * > Currently this only contains the `"Fixit Subscription"` Product object. */ -export const productsCache = new Cache([ - [FIXIT_SUBSCRIPTION_PRODUCT_NAME, fixitSubscriptionProduct], +export const productsCache = new Cache([ + [PRODUCT_NAMES.FIXIT_SUBSCRIPTION, fixitSubscriptionProduct], ]); diff --git a/src/lib/cache/promoCodesCache.ts b/src/lib/cache/promoCodesCache.ts index cdbaa3a7..54fe4eba 100644 --- a/src/lib/cache/promoCodesCache.ts +++ b/src/lib/cache/promoCodesCache.ts @@ -29,7 +29,7 @@ const initialCacheEntries: Array = activePromoCodes.map( /* The `coupon` object type-def specifies "discount" is a nullable float, but this app's promoCodes are all integers. If this is somehow not the case in the returned Stripe object(s), throw an error. */ - if (!isSafeInteger(coupon?.percent_off)) { + if (!isSafeInteger(coupon.percent_off)) { throw new InternalServerError( "Unable to initialize promoCodesCache — Stripe returned a PromoCode object " + `with an invalid "percent_off": ${safeJsonStringify(stripePromoCodeObject)}` diff --git a/src/lib/cache/usersCache.ts b/src/lib/cache/usersCache.ts index 56e0dc64..138699dc 100644 --- a/src/lib/cache/usersCache.ts +++ b/src/lib/cache/usersCache.ts @@ -1,7 +1,7 @@ +import { USER_ID_PREFIX_STR } from "@/models/User/helpers.js"; import { ddbTable } from "@/models/ddbTable.js"; -import { ENV } from "@/server/env"; import { Cache } from "./Cache.js"; -import type { UnaliasedUserItem } from "@/models/User/User.js"; +import type { UnaliasedUserItem } from "@/models/User"; import type { User, Contact } from "@/types/graphql.js"; import type { Simplify } from "type-fest"; @@ -10,35 +10,30 @@ export type UsersCacheEntry = [User["handle"], UsersCacheObject]; const initialCacheEntries: Array = []; -// In dev/staging/prod, initialize the usersCache with all users from the DDB table: -if (/^(dev|staging|prod)/.test(ENV.NODE_ENV)) { - const { Items: items = [] } = await ddbTable.ddbClient.scan({ - TableName: ddbTable.tableName, - ProjectionExpression: "pk, sk, #data, handle, phone, profile", - FilterExpression: "begins_with(pk, :user_pk_prefix)", - ExpressionAttributeNames: { - "#data": "data", - }, - ExpressionAttributeValues: { - ":user_pk_prefix": "USER#", - }, - }); +// Initialize the usersCache with all active users: +const { Items: items = [] } = await ddbTable.ddbClient.scan({ + TableName: ddbTable.tableName, + ProjectionExpression: "pk, sk, #data, handle, phone, profile", + FilterExpression: "begins_with(pk, :user_pk_prefix)", + ExpressionAttributeNames: { + "#data": "data", + }, + ExpressionAttributeValues: { + ":user_pk_prefix": `${USER_ID_PREFIX_STR}#`, + }, +}); - items.forEach((dbItem) => { - // prettier-ignore - const { +items.forEach((dbItem) => { + // prettier-ignore + const { pk: id, data: email, handle, phone = null, profile, createdAt, updatedAt, } = dbItem as UnaliasedUserItem - if (id && email && handle && profile && createdAt && updatedAt) { - // Only users' public fields are cached for search - initialCacheEntries.push([ - handle, - { id, email, handle, phone, profile, createdAt, updatedAt }, - ]); - } - }); -} + if (id && email && handle) { + // Only users' public fields are cached for search + initialCacheEntries.push([handle, { id, email, handle, phone, profile, createdAt, updatedAt }]); + } +}); /** * API local cache for searching Users by `User.handle`. diff --git a/src/lib/googleOAuth2Client/helpers.ts b/src/lib/googleOAuth2Client/helpers.ts deleted file mode 100644 index f6f1cb44..00000000 --- a/src/lib/googleOAuth2Client/helpers.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { - sanitizeID, - isValidID, - sanitizeEmail, - isValidEmail, - sanitizeAlphabetic, - isValidAlphabetic, - sanitizeURL, - isValidURL, -} from "@nerdware/ts-string-helpers"; -import { getTypeSafeError } from "@nerdware/ts-type-safety-utils"; -import { AuthError } from "@/utils/httpErrors.js"; -import { googleOAuth2Client } from "./googleOAuth2Client.js"; -import type { TokenPayload as GoogleOAuth2IDTokenPayload } from "google-auth-library"; - -/** - * This function validates and parses a Google OAuth2 ID token, including the - * relevant payload fields extracted from it. - * - * > **The structure of Google JWT ID tokens is available here:** - * > https://developers.google.com/identity/gsi/web/reference/js-reference#credential - * - * @see https://developers.google.com/identity/gsi/web/guides/verify-google-id-token - */ -export const parseGoogleOAuth2IDToken = async ( - rawGoogleIDToken: string -): Promise => { - // Initialize variable to hold the token payload: - let tokenPayload: GoogleOAuth2IDTokenPayload | undefined; - - try { - const ticket = await googleOAuth2Client.verifyIdToken({ idToken: rawGoogleIDToken }); - - tokenPayload = ticket.getPayload(); - } catch (err) { - // Re-throw as AuthError - throw new AuthError( - getTypeSafeError(err, { fallBackErrMsg: DEFAULT_GOOGLE_OAUTH_ERR_MSG }).message - ); - } - - if (!tokenPayload) throw new AuthError(DEFAULT_GOOGLE_OAUTH_ERR_MSG); - - const { - email: unsanitized_email, - sub: unsanitized_googleID, - given_name: unsanitized_givenName, - family_name: unsanitized_familyName, - picture: unsanitized_profilePhotoURL, - } = tokenPayload; - - // Ensure the payload includes an `email`: - if (!unsanitized_email) throw new AuthError(DEFAULT_GOOGLE_OAUTH_ERR_MSG); - - // Sanitize the relevant payload fields (optional fields use `let`, default to null if invalid) - - const email = sanitizeEmail(unsanitized_email); - const googleID = sanitizeID(unsanitized_googleID); - let givenName = unsanitized_givenName ? sanitizeAlphabetic(unsanitized_givenName) : null; - let familyName = unsanitized_familyName ? sanitizeAlphabetic(unsanitized_familyName) : null; - let profilePhotoUrl = unsanitized_profilePhotoURL - ? sanitizeURL(unsanitized_profilePhotoURL) - : null; - - // Validate the REQUIRED payload fields (if invalid, throw error) - if (!isValidEmail(email) || !isValidID(googleID)) { - throw new AuthError(DEFAULT_GOOGLE_OAUTH_ERR_MSG); - } - - // Validate the OPTIONAL payload fields (if invalid, set to null) - if (!isValidAlphabetic(givenName)) givenName = null; - if (!isValidAlphabetic(familyName)) familyName = null; - if (!isValidURL(profilePhotoUrl)) profilePhotoUrl = null; - - return { - _isValid: true, - email, - googleID, - profile: { - givenName, - familyName, - photoUrl: profilePhotoUrl, - }, - }; -}; - -const DEFAULT_GOOGLE_OAUTH_ERR_MSG = "Invalid credentials"; - -/** - * The fields returned by {@link parseGoogleOAuth2IDToken} - */ -export type ParsedGoogleOAuth2IDTokenFields = { - _isValid: boolean; - email: string; - googleID: string; - profile: { - givenName: string | null; - familyName: string | null; - photoUrl: string | null; - }; -}; diff --git a/src/lib/googleOAuth2Client/index.ts b/src/lib/googleOAuth2Client/index.ts index 39bf215b..7974919c 100644 --- a/src/lib/googleOAuth2Client/index.ts +++ b/src/lib/googleOAuth2Client/index.ts @@ -1,2 +1 @@ export * from "./googleOAuth2Client.js"; -export * from "./helpers.js"; diff --git a/src/lib/pinpointClient/helpers.ts b/src/lib/pinpointClient/helpers.ts new file mode 100644 index 00000000..3f41f0c0 --- /dev/null +++ b/src/lib/pinpointClient/helpers.ts @@ -0,0 +1,29 @@ +import { isString } from "@nerdware/ts-type-safety-utils"; +import type { AddressConfiguration, ChannelType } from "@aws-sdk/client-pinpoint"; + +/** + * An object defining the `to` field, the value of which must be one or more recipients for + * a message. The format of each `to` value depends on the `ChannelType` of the message. + */ +export type PinpointMessageTo = { + to?: string | Array; + ChannelType?: Ch; + [key: string]: unknown; +}; + +/** + * Pinpoint client helper for creating `Addresses` that all have the same `ChannelType`. + */ +export const fmtMessageAddresses = ({ + to, + ChannelType, +}: Required): Record => { + const recipientsArray = isString(to) ? [to] : to; + // prettier-ignore + return recipientsArray.reduce( + (accum: Record, toAddress) => { + accum[toAddress] = { ChannelType }; + return accum; + }, {} + ); +}; diff --git a/src/lib/pinpointClient/index.ts b/src/lib/pinpointClient/index.ts new file mode 100644 index 00000000..4f209d0d --- /dev/null +++ b/src/lib/pinpointClient/index.ts @@ -0,0 +1,2 @@ +export * from "./pinpointClient.js"; +export * from "./helpers"; diff --git a/src/lib/pinpointClient/pinpointClient.ts b/src/lib/pinpointClient/pinpointClient.ts new file mode 100644 index 00000000..88d89970 --- /dev/null +++ b/src/lib/pinpointClient/pinpointClient.ts @@ -0,0 +1,119 @@ +import { + PinpointClient, + SendMessagesCommand, + type MessageRequest, + type ChannelType, + type TemplateConfiguration, + type Template, +} from "@aws-sdk/client-pinpoint"; +import { getErrorMessage } from "@nerdware/ts-type-safety-utils"; +import { ENV } from "@/server/env"; +import { logger } from "@/utils/logger.js"; +import { fmtMessageAddresses, type PinpointMessageTo } from "./helpers.js"; +import type { OverrideProperties } from "type-fest"; + +const _pinpointClient = new PinpointClient({ region: ENV.AWS.REGION }); + +/** + * App-specific Pinpoint templates. + */ +export const APP_TEMPLATES = { + EMAIL: { + WELCOME: { + Name: "new-user-welcome-email", + }, + CHECKOUT_CONFIRMATION: { + Name: "checkout-confirmation-email", + }, + PASSWORD_RESET: { + Name: "password-reset-email", + }, + INVITE: { + Name: "fixit-user-invitation-email", + }, + }, + SMS: { + INVITE: { + Name: "fixit-user-invitation-sms", + }, + }, +} as const satisfies { [Ch in ChannelType]?: Record }; + +type AppTemplatesDict = typeof APP_TEMPLATES; +type AppTemplatesChannelType = keyof AppTemplatesDict; +type EmailTemplateName = AppTemplatesDict["EMAIL"][keyof AppTemplatesDict["EMAIL"]]["Name"]; +type SMSTemplateName = AppTemplatesDict["SMS"][keyof AppTemplatesDict["SMS"]]["Name"]; + +/** + * Mapping of supported Pinpoint channel-types to the corresponding template configuration key. + */ +type ChannelTypeToTemplateConfigKey = { + EMAIL: "EmailTemplate"; + SMS: "SMSTemplate"; +}; + +/** + * Parameter typing for {@link pinpointClient.sendMessages}. + */ +type SendAppMessagesParams = PinpointMessageTo & + (Ch extends AppTemplatesChannelType + ? OverrideProperties< + MessageRequest, + { + TemplateConfiguration: { + [TemplateConfigKey in keyof TemplateConfiguration as TemplateConfigKey extends ChannelTypeToTemplateConfigKey[Ch] + ? TemplateConfigKey + : never]?: Ch extends "EMAIL" + ? { Name: EmailTemplateName; Version?: string } + : Ch extends "SMS" + ? { Name: SMSTemplateName; Version?: string } + : never; + }; + } + > + : never); + +/** + * Amazon Pinpoint client with wrapper methods for simplified messaging. + */ +export const pinpointClient = { + /** + * Thin wrapper around {@link SendMessagesCommand} + * - Creates a new `SendMessagesCommand` instance with the app's Pinpoint project ID on every call. + * - Narrows {@link Template} params to only permit the app's existing Pinpoint message templates. + * + * > **Note:** If a template `Version` is not provided, it defaults to the template's `"Active"` version. + * > + * > See [AWS Docs: Designating the Active version of a message template][template-version-docs]. + * + * [template-version-docs]: https://docs.aws.amazon.com/pinpoint/latest/userguide/message-templates-versioning.html#message-templates-versioning-set-active + */ + sendMessages: async ({ + to, + ChannelType, + ...messageRequest + }: SendAppMessagesParams) => { + return await _pinpointClient + .send( + new SendMessagesCommand({ + ApplicationId: ENV.AWS.PINPOINT_PROJECT_ID, + MessageRequest: { + ...(!!to && !!ChannelType && { Addresses: fmtMessageAddresses({ to, ChannelType }) }), + ...messageRequest, + } as MessageRequest, + }) + ) + .catch((error: unknown) => { + const errMsg = getErrorMessage(error) ?? "(Unknown error)"; + /* Currently, the Pinpoint client is not provided with adequate permissions + to send messages in the dev environment. Logging `errMsg` is therefore + conditional in order to avoid cluttering the console with expected errors. */ + if ( + ENV.IS_DEPLOYED_ENV || + errMsg !== "The security token included in the request is invalid." + ) { + logger.error(`Error sending Pinpoint message: ${errMsg}`); + } + }); + }, +} as const; diff --git a/src/lib/stripe/__mocks__/_mockStripeAddress.ts b/src/lib/stripe/__mocks__/_mockStripeAddress.ts new file mode 100644 index 00000000..2578a7c7 --- /dev/null +++ b/src/lib/stripe/__mocks__/_mockStripeAddress.ts @@ -0,0 +1,17 @@ +import type Stripe from "stripe"; + +/** + * Returns a mock Stripe Address object. + * @see https://docs.stripe.com/api/terminal/locations/create + */ +export const mockStripeAddress = ( + mockAddressParams: Partial = {} +): Stripe.Address => ({ + country: null, + state: null, + city: null, + postal_code: null, + line1: null, + line2: null, + ...mockAddressParams, +}); diff --git a/src/lib/stripe/__mocks__/_mockStripeBillingPortalSession.ts b/src/lib/stripe/__mocks__/_mockStripeBillingPortalSession.ts index fd5f992a..9e623a22 100644 --- a/src/lib/stripe/__mocks__/_mockStripeBillingPortalSession.ts +++ b/src/lib/stripe/__mocks__/_mockStripeBillingPortalSession.ts @@ -1,5 +1,5 @@ import dayjs from "dayjs"; -import type { UserItem } from "@/models/User/User.js"; +import type { UserItem } from "@/models/User"; import type Stripe from "stripe"; /** diff --git a/src/lib/stripe/__mocks__/_mockStripeConnectAccount.ts b/src/lib/stripe/__mocks__/_mockStripeConnectAccount.ts index 198e644f..49d6f61b 100644 --- a/src/lib/stripe/__mocks__/_mockStripeConnectAccount.ts +++ b/src/lib/stripe/__mocks__/_mockStripeConnectAccount.ts @@ -1,116 +1,107 @@ import dayjs from "dayjs"; -import merge from "lodash.merge"; -import type { UserItem } from "@/models/User/User.js"; -import type { UserStripeConnectAccountItem } from "@/models/UserStripeConnectAccount/UserStripeConnectAccount.js"; +import type { UserItem } from "@/models/User"; +import type { UserStripeConnectAccountItem } from "@/models/UserStripeConnectAccount"; import type Stripe from "stripe"; -import type { PartialDeep } from "type-fest"; /** * Returns a mock Stripe Connect Account object. * @see https://stripe.com/docs/api/accounts/object */ -export const mockStripeConnectAccount = ( - { - // SCA fields: - id: stripeConnectAccountID, - chargesEnabled, - payoutsEnabled, - detailsSubmitted, - createdAt, - // optional User fields: - email = "mock_user_email@gmail.com", - phone = "1234567890", - profile = { displayName: "Mock User" }, - }: UserStripeConnectAccountItem & Partial>, - customValues?: PartialDeep | null -): Stripe.Account => { - // Default mock Account object - const defaultMockAccountObj: Stripe.Account = { - object: "account", - type: "express", - id: stripeConnectAccountID, - charges_enabled: chargesEnabled, - details_submitted: payoutsEnabled, - payouts_enabled: detailsSubmitted, - email, - created: dayjs(createdAt).unix(), - country: "US", - default_currency: "usd", - capabilities: { card_payments: "active", transfers: "active" }, - // The fields below are not currently used by the app, but are included here for completeness. - business_profile: { - mcc: null, - name: profile?.businessName ?? null, - product_description: null, - support_address: null, - support_email: email, - support_phone: phone, - support_url: null, - url: null, +export const mockStripeConnectAccount = ({ + // SCA fields: + id: stripeConnectAccountID, + chargesEnabled, + payoutsEnabled, + detailsSubmitted, + createdAt, + // optional User fields: + email = "mock_user_email@gmail.com", + phone = "1234567890", + profile = { displayName: "Mock User" }, +}: UserStripeConnectAccountItem & + Partial>): Stripe.Account => ({ + object: "account", + type: "express", + id: stripeConnectAccountID, + charges_enabled: chargesEnabled, + details_submitted: payoutsEnabled, + payouts_enabled: detailsSubmitted, + email, + created: dayjs(createdAt).unix(), + country: "US", + default_currency: "usd", + capabilities: { card_payments: "active", transfers: "active" }, + // The fields below are not currently used by the app, but are included here for completeness. + business_profile: { + mcc: null, + name: profile.businessName ?? null, + product_description: null, + support_address: null, + support_email: email, + support_phone: phone, + support_url: null, + url: null, + }, + external_accounts: { + object: "list", + data: [], + has_more: false, + url: `/v1/accounts/${stripeConnectAccountID}/external_accounts`, + }, + future_requirements: { + alternatives: [], + current_deadline: null, + currently_due: [], + disabled_reason: null, + errors: [], + eventually_due: [], + past_due: [], + pending_verification: [], + }, + metadata: {}, + requirements: { + alternatives: [], + current_deadline: null, + currently_due: [], + disabled_reason: null, + errors: [], + eventually_due: [], + past_due: [], + pending_verification: [], + }, + settings: { + bacs_debit_payments: {}, + branding: { + icon: null, + logo: null, + primary_color: null, + secondary_color: null, }, - external_accounts: { - object: "list", - data: [], - has_more: false, - url: `/v1/accounts/${stripeConnectAccountID}/external_accounts`, + card_issuing: { tos_acceptance: { date: null, ip: null } }, + card_payments: { + decline_on: { avs_failure: true, cvc_failure: false }, + statement_descriptor_prefix: null, + statement_descriptor_prefix_kanji: null, + statement_descriptor_prefix_kana: null, }, - future_requirements: { - alternatives: [], - current_deadline: null, - currently_due: [], - disabled_reason: null, - errors: [], - eventually_due: [], - past_due: [], - pending_verification: [], + dashboard: { display_name: profile.displayName, timezone: "US/Pacific" }, + payments: { + statement_descriptor: null, + statement_descriptor_kana: null, + statement_descriptor_kanji: null, + statement_descriptor_prefix_kana: null, + statement_descriptor_prefix_kanji: null, }, - metadata: {}, - requirements: { - alternatives: [], - current_deadline: null, - currently_due: [], - disabled_reason: null, - errors: [], - eventually_due: [], - past_due: [], - pending_verification: [], + payouts: { + debit_negative_balances: true, + schedule: { delay_days: 7, interval: "daily" }, + statement_descriptor: null, }, - settings: { - bacs_debit_payments: {}, - branding: { - icon: null, - logo: null, - primary_color: null, - secondary_color: null, - }, - card_issuing: { tos_acceptance: { date: null, ip: null } }, - card_payments: { - decline_on: { avs_failure: true, cvc_failure: false }, - statement_descriptor_prefix: null, - statement_descriptor_prefix_kanji: null, - statement_descriptor_prefix_kana: null, - }, - dashboard: { display_name: profile.displayName, timezone: "US/Pacific" }, - payments: { - statement_descriptor: null, - statement_descriptor_kana: null, - statement_descriptor_kanji: null, - statement_descriptor_prefix_kana: null, - statement_descriptor_prefix_kanji: null, - }, - payouts: { - debit_negative_balances: true, - schedule: { delay_days: 7, interval: "daily" }, - statement_descriptor: null, - }, - sepa_debit_payments: {}, - }, - tos_acceptance: { - date: null, - ip: null, - user_agent: null, - }, - }; - - return customValues ? merge(defaultMockAccountObj, customValues) : defaultMockAccountObj; -}; + sepa_debit_payments: {}, + }, + tos_acceptance: { + date: null, + ip: null, + user_agent: null, + }, +}); diff --git a/src/lib/stripe/__mocks__/_mockStripeCustomer.ts b/src/lib/stripe/__mocks__/_mockStripeCustomer.ts index 22cb162b..00e2af03 100644 --- a/src/lib/stripe/__mocks__/_mockStripeCustomer.ts +++ b/src/lib/stripe/__mocks__/_mockStripeCustomer.ts @@ -1,8 +1,8 @@ import dayjs from "dayjs"; -import merge from "lodash.merge"; +import deepMerge from "lodash.merge"; import { mockStripeSubscription } from "./_mockStripeSubscription.js"; -import type { UserItem } from "@/models/User/User.js"; -import type { UserSubscriptionItem } from "@/models/UserSubscription/UserSubscription.js"; +import type { UserItem } from "@/models/User"; +import type { UserSubscriptionItem } from "@/models/UserSubscription"; import type Stripe from "stripe"; /** @@ -19,7 +19,7 @@ export const mockStripeCustomer = ( customerUpdateParams?.invoice_settings?.default_payment_method ?? "pm_TestTestTest"; // Default mock Customer object - const defaultMockCustomerObj: Stripe.Customer = { + const defaultMockCustomerObj = { object: "customer", id: stripeCustomerID, email, @@ -43,10 +43,10 @@ export const mockStripeCustomer = ( livemode: false, metadata: {}, shipping: null, - }; + } as const satisfies Stripe.Customer; return customerUpdateParams - ? merge(defaultMockCustomerObj, customerUpdateParams) + ? deepMerge(defaultMockCustomerObj, customerUpdateParams) : defaultMockCustomerObj; }; diff --git a/src/lib/stripe/__mocks__/_mockStripeInvoice.ts b/src/lib/stripe/__mocks__/_mockStripeInvoice.ts index 210239a6..ea083107 100644 --- a/src/lib/stripe/__mocks__/_mockStripeInvoice.ts +++ b/src/lib/stripe/__mocks__/_mockStripeInvoice.ts @@ -1,8 +1,8 @@ import dayjs from "dayjs"; -import merge from "lodash.merge"; +import deepMerge from "lodash.merge"; import { mockStripePaymentIntent } from "./_mockStripePaymentIntent.js"; -import type { UserItem } from "@/models/User/User.js"; -import type { UserSubscriptionItem } from "@/models/UserSubscription/UserSubscription.js"; +import type { UserItem } from "@/models/User"; +import type { UserSubscriptionItem } from "@/models/UserSubscription"; import type Stripe from "stripe"; import type { PartialDeep } from "type-fest"; @@ -154,7 +154,5 @@ export const mockStripeInvoice = ( webhooks_delivered_at: null, }; - return otherCustomValues - ? merge(defaultMockInvoiceObj, otherCustomValues) - : defaultMockInvoiceObj; + return deepMerge(defaultMockInvoiceObj, otherCustomValues); }; diff --git a/src/lib/stripe/__mocks__/_mockStripePaymentIntent.ts b/src/lib/stripe/__mocks__/_mockStripePaymentIntent.ts index c708f159..627885e6 100644 --- a/src/lib/stripe/__mocks__/_mockStripePaymentIntent.ts +++ b/src/lib/stripe/__mocks__/_mockStripePaymentIntent.ts @@ -1,5 +1,5 @@ import dayjs from "dayjs"; -import merge from "lodash.merge"; +import deepMerge from "lodash.merge"; import type Stripe from "stripe"; import type { PartialDeep, SetOptional } from "type-fest"; @@ -72,6 +72,6 @@ export const mockStripePaymentIntent = ( }; return customValues - ? merge(defaultMockPaymentIntentObj, customValues) + ? deepMerge(defaultMockPaymentIntentObj, customValues) : defaultMockPaymentIntentObj; }; diff --git a/src/lib/stripe/__mocks__/_mockStripePaymentMethod.ts b/src/lib/stripe/__mocks__/_mockStripePaymentMethod.ts index a54bd2fb..f60a9b9c 100644 --- a/src/lib/stripe/__mocks__/_mockStripePaymentMethod.ts +++ b/src/lib/stripe/__mocks__/_mockStripePaymentMethod.ts @@ -1,6 +1,6 @@ import dayjs from "dayjs"; -import merge from "lodash.merge"; -import type { UserItem } from "@/models/User/User.js"; +import deepMerge from "lodash.merge"; +import type { UserItem } from "@/models/User"; import type Stripe from "stripe"; import type { SetRequired } from "type-fest"; @@ -53,7 +53,5 @@ export const mockStripePaymentMethod = ( metadata: {}, }; - return customValues - ? merge(defaultMockPaymentMethodObj, customValues) - : defaultMockPaymentMethodObj; + return deepMerge(defaultMockPaymentMethodObj, customValues); }; diff --git a/src/lib/stripe/__mocks__/_mockStripePrice.ts b/src/lib/stripe/__mocks__/_mockStripePrice.ts index bca969d1..eacdd0f4 100644 --- a/src/lib/stripe/__mocks__/_mockStripePrice.ts +++ b/src/lib/stripe/__mocks__/_mockStripePrice.ts @@ -1,4 +1,5 @@ import deepMerge from "lodash.merge"; +import { SUBSCRIPTION_PRICE_NAMES } from "@/models/UserSubscription/enumConstants.js"; import { MOCK_DATE_UNIX_TIMESTAMPS } from "@/tests/staticMockItems/dates.js"; import type Stripe from "stripe"; import type { PartialDeep } from "type-fest"; @@ -9,7 +10,9 @@ import type { PartialDeep } from "type-fest"; * * @see https://stripe.com/docs/api/prices/object */ -export const mockStripePrice = ({ ...priceArgs }: PartialDeep) => { +export const mockStripePrice = ({ ...priceArgs }: PartialDeep): Stripe.Price => { + /* deepMerge does NOT create a new obj, it updates+returns the first arg's + ref, so the DEFAULT_ object must be spread here to avoid mutating it. */ return deepMerge({ ...DEFAULT_MOCK_STRIPE_PRICE_FIELDS }, priceArgs); }; @@ -18,16 +21,17 @@ export const mockStripePrice = ({ ...priceArgs }: PartialDeep) => */ const DEFAULT_MOCK_STRIPE_PRICE_FIELDS: Stripe.Price = { object: "price", - id: "price_TestANNUAL", + id: `price_Test${SUBSCRIPTION_PRICE_NAMES.ANNUAL}`, active: true, billing_scheme: "per_unit", created: MOCK_DATE_UNIX_TIMESTAMPS.JAN_1_2020, currency: "usd", + currency_options: {}, custom_unit_amount: null, livemode: false, lookup_key: null, metadata: {}, - nickname: "ANNUAL", + nickname: SUBSCRIPTION_PRICE_NAMES.ANNUAL, product: "prod_TestTestTest", recurring: { aggregate_usage: null, diff --git a/src/lib/stripe/__mocks__/_mockStripeProduct.ts b/src/lib/stripe/__mocks__/_mockStripeProduct.ts index d4103d55..4177a40d 100644 --- a/src/lib/stripe/__mocks__/_mockStripeProduct.ts +++ b/src/lib/stripe/__mocks__/_mockStripeProduct.ts @@ -1,6 +1,5 @@ import deepMerge from "lodash.merge"; import type Stripe from "stripe"; -import type { PartialDeep } from "type-fest"; /** * Returns a mock Stripe Product object. Any provided args are deep-merged with @@ -8,7 +7,9 @@ import type { PartialDeep } from "type-fest"; * * @see https://stripe.com/docs/api/products/object */ -export const mockStripeProduct = ({ ...productArgs }: PartialDeep) => { +export const mockStripeProduct = ({ ...productArgs }: Partial): Stripe.Product => { + /* deepMerge does NOT create a new obj, it updates+returns the first arg's + ref, so the DEFAULT_ object must be spread here to avoid mutating it. */ return deepMerge({ ...DEFAULT_MOCK_STRIPE_PRODUCT_FIELDS }, productArgs); }; @@ -21,8 +22,7 @@ const DEFAULT_MOCK_STRIPE_PRODUCT_FIELDS: Stripe.Product = { active: true, created: 1678833149, default_price: null, - description: - "People who need to get things done use Fixit to keep in touch with customers and contractors, create work orders, submit invoices, and manage payments - all in one place.", + description: "The payment app for people who need to get things done.", images: [], livemode: false, metadata: {}, diff --git a/src/lib/stripe/__mocks__/_mockStripePromotionCode.ts b/src/lib/stripe/__mocks__/_mockStripePromotionCode.ts index ab174fc2..65178429 100644 --- a/src/lib/stripe/__mocks__/_mockStripePromotionCode.ts +++ b/src/lib/stripe/__mocks__/_mockStripePromotionCode.ts @@ -11,6 +11,8 @@ import type { PartialDeep } from "type-fest"; export const mockStripePromotionCode = ({ ...promoCodeArgs }: PartialDeep): Stripe.PromotionCode => { + /* deepMerge does NOT create a new obj, it updates+returns the first arg's + ref, so the DEFAULT_ object must be spread here to avoid mutating it. */ return deepMerge({ ...DEFAULT_MOCK_PROMO_CODE_FIELDS }, promoCodeArgs); }; diff --git a/src/lib/stripe/__mocks__/_mockStripeSubscription.ts b/src/lib/stripe/__mocks__/_mockStripeSubscription.ts index caa208ff..5bd65c76 100644 --- a/src/lib/stripe/__mocks__/_mockStripeSubscription.ts +++ b/src/lib/stripe/__mocks__/_mockStripeSubscription.ts @@ -1,12 +1,12 @@ import { isPlainObject, isString } from "@nerdware/ts-type-safety-utils"; import dayjs from "dayjs"; -import merge from "lodash.merge"; +import deepMerge from "lodash.merge"; import { mockStripeInvoice } from "./_mockStripeInvoice.js"; import { mockStripePaymentIntent } from "./_mockStripePaymentIntent.js"; import { MOCK_STRIPE_PLAN } from "./_mockStripePlan.js"; import { mockStripePrice } from "./_mockStripePrice.js"; -import type { UserItem } from "@/models/User/User.js"; -import type { UserSubscriptionItem } from "@/models/UserSubscription/UserSubscription.js"; +import type { UserItem } from "@/models/User"; +import type { UserSubscriptionItem } from "@/models/UserSubscription"; import type Stripe from "stripe"; import type { PartialDeep } from "type-fest"; @@ -27,7 +27,7 @@ export const mockStripeSubscription = ( const currentPeriodStart = currentPeriodEndDayObject.subtract(1, "month").unix(); // <-- 1 month is arbitrary here const defaultPaymentMethodID: string = - isPlainObject(default_payment_method) && isString(default_payment_method?.id) + isPlainObject(default_payment_method) && isString(default_payment_method.id) ? default_payment_method.id : isString(default_payment_method) ? default_payment_method @@ -114,7 +114,5 @@ export const mockStripeSubscription = ( trial_start: null, }; - return customValues - ? merge(defaultMockSubscriptionObj, customValues) - : defaultMockSubscriptionObj; + return deepMerge(defaultMockSubscriptionObj, customValues); }; diff --git a/src/lib/stripe/__mocks__/stripeClient.ts b/src/lib/stripe/__mocks__/stripeClient.ts index 57810538..a95a12e1 100644 --- a/src/lib/stripe/__mocks__/stripeClient.ts +++ b/src/lib/stripe/__mocks__/stripeClient.ts @@ -1,3 +1,7 @@ +import { + SUBSCRIPTION_PRODUCT_NAMES as SUB_PRODUCT_NAMES, + SUBSCRIPTION_PRICE_NAMES as SUB_PRICE_NAMES, +} from "@/models/UserSubscription/enumConstants.js"; import { findMock } from "@/tests/staticMockItems/_helpers.js"; import { mockStripeBillingPortalSession } from "./_mockStripeBillingPortalSession.js"; import { mockStripeConnectAccount } from "./_mockStripeConnectAccount.js"; @@ -27,6 +31,8 @@ import type { SetRequired, AsyncReturnType } from "type-fest"; * like `RequestOptions` (which this app never uses) instead of something like the * `CustomerCreateParams` object. So instead we grab the 1st overload with util type * {@link ParamsOfFirstOverload}. + * + * // TODO Replace this mock Stripe client with individual mocks in each respective test (rm dep) */ export const stripe: MockStripeAPI = { accountLinks: { @@ -172,13 +178,13 @@ export const stripe: MockStripeAPI = { object: "list", data: [ mockStripePrice({ - id: "price_TestMONTHLY", - nickname: "MONTHLY", + id: `price_Test${SUB_PRICE_NAMES.MONTHLY}`, + nickname: SUB_PRICE_NAMES.MONTHLY, recurring: { interval: "month" }, }), mockStripePrice({ - id: "price_TestANNUAL", - nickname: "ANNUAL", + id: `price_Test${SUB_PRICE_NAMES.ANNUAL}`, + nickname: SUB_PRICE_NAMES.ANNUAL, recurring: { interval: "year" }, }), ], @@ -189,7 +195,12 @@ export const stripe: MockStripeAPI = { list: async () => { return Promise.resolve({ object: "list", - data: [mockStripeProduct({ name: "Fixit Subscription", id: "prod_TestTestTest" })], + data: [ + mockStripeProduct({ + name: SUB_PRODUCT_NAMES.FIXIT_SUBSCRIPTION, + id: "prod_TestTestTest", + }), + ], }); }, }, diff --git a/src/lib/stripe/isValidStripeID.ts b/src/lib/stripe/helpers.ts similarity index 62% rename from src/lib/stripe/isValidStripeID.ts rename to src/lib/stripe/helpers.ts index 855df4a1..e9ec9da5 100644 --- a/src/lib/stripe/isValidStripeID.ts +++ b/src/lib/stripe/helpers.ts @@ -1,4 +1,4 @@ -import { getRegexValidatorFn } from "@nerdware/ts-string-helpers"; +import { getValidatorFn } from "@nerdware/ts-string-helpers"; /** * An object with methods which return `true` if the given value is a string which @@ -12,17 +12,23 @@ import { getRegexValidatorFn } from "@nerdware/ts-string-helpers"; */ export const isValidStripeID = { /** Returns `true` if `value` is a valid Stripe ConnectAccount ID (e.g., `"acct_123abc123ABC"`). */ - connectAccount: getRegexValidatorFn(/^acct_[a-zA-Z0-9]{10,150}$/), + connectAccount: getValidatorFn(/^acct_[a-zA-Z0-9]{10,150}$/), /** Returns `true` if `value` is a valid Stripe Customer ID (e.g., `"cus_123abc123ABC"`). */ - customer: getRegexValidatorFn(/^cus_[a-zA-Z0-9]{10,150}$/), + customer: getValidatorFn(/^cus_[a-zA-Z0-9]{10,150}$/), /** Returns `true` if `value` is a valid Stripe PaymentIntent ID (e.g., `"pi_123abc123ABC"`). */ - paymentIntent: getRegexValidatorFn(/^pi_[a-zA-Z0-9]{10,150}$/), + paymentIntent: getValidatorFn(/^pi_[a-zA-Z0-9]{10,150}$/), /** Returns `true` if `value` is a valid Stripe PaymentMethod ID (e.g., `"pm_123abc123ABC"`). */ - paymentMethod: getRegexValidatorFn(/^pm_[a-zA-Z0-9]{10,150}$/), + paymentMethod: getValidatorFn(/^pm_[a-zA-Z0-9]{10,150}$/), /** Returns `true` if `value` is a valid Stripe Price ID (e.g., `"price_123abc123ABC"`). */ - price: getRegexValidatorFn(/^price_[a-zA-Z0-9]{10,150}$/), + price: getValidatorFn(/^price_[a-zA-Z0-9]{10,150}$/), /** Returns `true` if `value` is a valid Stripe Product ID (e.g., `"prod_123abc123ABC"`). */ - product: getRegexValidatorFn(/^prod_[a-zA-Z0-9]{10,150}$/), + product: getValidatorFn(/^prod_[a-zA-Z0-9]{10,150}$/), /** Returns `true` if `value` is a valid Stripe Subscription ID (e.g., `"sub_123abc123ABC"`). */ - subscription: getRegexValidatorFn(/^sub_[a-zA-Z0-9]{10,150}$/), + subscription: getValidatorFn(/^sub_[a-zA-Z0-9]{10,150}$/), }; + +/** + * Returns a sanitized Stripe ID string, with any chars removed which are + * neither alphanumeric nor an underscore (`_`). + */ +export const sanitizeStripeID = (value: string) => value.replace(/[^a-zA-Z0-9_]/g, ""); diff --git a/src/lib/stripe/stripeClient.ts b/src/lib/stripe/stripeClient.ts index 57e6c46e..befa9977 100644 --- a/src/lib/stripe/stripeClient.ts +++ b/src/lib/stripe/stripeClient.ts @@ -1,7 +1,7 @@ import Stripe, { type Stripe as StripeTypeNamespace } from "stripe"; import { ENV } from "@/server/env"; -if (!ENV?.STRIPE?.SECRET_KEY) { +if (!ENV.STRIPE.SECRET_KEY) { throw new Error("Unable to initialize Stripe client"); } diff --git a/src/lib/stripe/types.ts b/src/lib/stripe/types.ts index 6eff8789..8a2e3536 100644 --- a/src/lib/stripe/types.ts +++ b/src/lib/stripe/types.ts @@ -2,51 +2,60 @@ import type Stripe from "stripe"; import type { OverrideProperties, SetNonNullable } from "type-fest"; /** - * This generic util takes a Stripe object with a `"client_secret"` property - * (e.g., a {@link Stripe.PaymentIntent} or {@link Stripe.SetupIntent}), and - * makes the property non-nullable (converts the value to `string`). + * An expanded {@link Stripe.Customer} object with the fields necessary to + * obtain a `client_secret`. **This type can _only_ be used when the Stripe API + * call is provided with an `expand` arg which includes the following field:** + * + * ```ts + * // Example usage: + * const customer = (await stripe.customers.create({ + * // ... other args ... + * expand: [ + * "subscriptions.data.latest_invoice.payment_intent" + * ] + * })) as Stripe.Response; + * ``` */ -type NonNullableClientSecret = SetNonNullable< - T, - "client_secret" +export type StripeCustomerWithClientSecret = OverrideProperties< + Stripe.Customer, + { subscriptions: Stripe.ApiList } >; -/** {@link Stripe.PaymentIntent} with a non-nullable `client_secret` (`client_secret: string`). */ -export type StripePaymentIntentWithClientSecret = NonNullableClientSecret; - -/** {@link Stripe.SetupIntent} with a non-nullable `client_secret` (`client_secret: string`). */ -export type StripeSetupIntentWithClientSecret = NonNullableClientSecret; - /** - * This type reflects a {@link Stripe.Subscription} object whereby the fields which are - * necessary to obtain a `client_secret` have been expanded: - * - * - `latest_invoice.payment_intent` - * - `pending_setup_intent` + * An expanded {@link Stripe.Subscription} object with the fields necessary to + * obtain a `client_secret`. **This type can _only_ be used when the Stripe API + * call is provided with an `expand` arg which includes the following fields:** * - * For both of these expanded objects, the `client_secret` property is made non-nullable. + * ```ts + * // Example usage: + * const subscription = (await stripe.subscriptions.create({ + * // ... other args ... + * expand: [ + * "latest_invoice.payment_intent", + * "pending_setup_intent" + * ] + * })) as Stripe.Response; + * ``` */ export type StripeSubscriptionWithClientSecret = OverrideProperties< Stripe.Subscription, { - latest_invoice: OverrideProperties< - Stripe.Invoice, - { payment_intent: StripePaymentIntentWithClientSecret } - >; - pending_setup_intent?: StripeSetupIntentWithClientSecret; + latest_invoice: StripeInvoiceWithClientSecret; + pending_setup_intent: StripeSetupIntentWithClientSecret; } >; -/** - * This type reflects a {@link Stripe.Customer} object whereby the fields which are - * necessary to obtain a `client_secret` have been expanded: - * - * - `latest_invoice.payment_intent` - * - `pending_setup_intent` - * - * For both of these expanded objects, the `client_secret` property is made non-nullable. - */ -export type StripeCustomerWithClientSecret = OverrideProperties< - Stripe.Customer, - { subscriptions: Stripe.ApiList } +/** See type {@link StripeSubscriptionWithClientSecret} */ +export type StripeInvoiceWithClientSecret = OverrideProperties< + Stripe.Invoice, + { payment_intent: StripePaymentIntentWithClientSecret | null } >; + +/** See type {@link StripeInvoiceWithClientSecret} */ +export type StripePaymentIntentWithClientSecret = SetNonNullable< + Stripe.PaymentIntent, + "client_secret" +>; + +/** See type {@link StripeSubscriptionWithClientSecret} */ +export type StripeSetupIntentWithClientSecret = SetNonNullable; diff --git a/src/middleware/auth/findUserByEmail.ts b/src/middleware/auth/findUserByEmail.ts deleted file mode 100644 index ebf16fa0..00000000 --- a/src/middleware/auth/findUserByEmail.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; -import { User } from "@/models/User/User.js"; -import type { RestApiRequestBodyByPath } from "@/types/open-api.js"; - -/** - * This middleware simply queries the DB for a User with the given email address. - */ -export const findUserByEmail = mwAsyncCatchWrapper< - Pick ->(async (req, res, next) => { - const [user] = await User.query({ - where: { email: req.body.email }, - limit: 1, - }); - - res.locals.user = user; - - next(); -}); diff --git a/src/middleware/auth/generateAuthToken.ts b/src/middleware/auth/generateAuthToken.ts deleted file mode 100644 index cd7a4876..00000000 --- a/src/middleware/auth/generateAuthToken.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { AuthToken } from "@/utils/AuthToken.js"; -import { AuthError } from "@/utils/httpErrors.js"; -import type { RestApiRequestHandler } from "@/middleware/helpers.js"; - -/** - * This middleware generates an AuthToken for the authenticated User to be - * included in the returned response. If the User is not found or the User's - * Stripe Connect account is not found, an error message is passed to `next`. - */ -export const generateAuthToken: RestApiRequestHandler = (req, res, next) => { - const { authenticatedUser } = res.locals; - - if (!authenticatedUser?.id) return next(new AuthError("User not found")); - - const authToken = new AuthToken(authenticatedUser); - - res.locals.authToken = authToken.toString(); - - next(); -}; diff --git a/src/middleware/auth/getUserFromAuthHeaderToken.ts b/src/middleware/auth/getUserFromAuthHeaderToken.ts deleted file mode 100644 index b3774b71..00000000 --- a/src/middleware/auth/getUserFromAuthHeaderToken.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; -import { AuthToken } from "@/utils/AuthToken.js"; -import { AuthError } from "@/utils/httpErrors.js"; - -/** - * Authentication middleware that checks for a valid auth token in the request - * header. If a valid token is found, the user's info is added to the request - * object as `res.locals.authenticatedUser`. - * - * > _All authentication-required logic/features are placed after this mw._ - */ -export const getUserFromAuthHeaderToken = mwAsyncCatchWrapper(async (req, res, next) => { - res.locals.authenticatedUser = await AuthToken.getValidatedRequestAuthTokenPayload(req).catch( - (err) => { - throw new AuthError(err); // Re-throw as AuthError - } - ); - - next(); -}); diff --git a/src/middleware/auth/index.ts b/src/middleware/auth/index.ts deleted file mode 100644 index 0a3a6257..00000000 --- a/src/middleware/auth/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from "./findUserByEmail.js"; -export * from "./generateAuthToken.js"; -export * from "./getUserFromAuthHeaderToken.js"; -export * from "./parseGoogleIDToken.js"; -export * from "./queryUserItems.js"; -export * from "./registerNewUser.js"; -export * from "./shouldUserLoginExist.js"; -export * from "./validateGqlReqContext.js"; -export * from "./validateLogin.js"; diff --git a/src/middleware/auth/parseGoogleIDToken.ts b/src/middleware/auth/parseGoogleIDToken.ts deleted file mode 100644 index e2a1c9c2..00000000 --- a/src/middleware/auth/parseGoogleIDToken.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { parseGoogleOAuth2IDToken } from "@/lib/googleOAuth2Client"; -import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; -import type { RestApiRequestBodyByPath } from "@/types/open-api.js"; - -/** - * This middleware parses and validates a `googleIDToken` if provided. If valid, - * it is decoded to obtain the fields listed below. These fields are then used to - * create login args, which are added to `res.locals.googleIDTokenFields` and - * `req.body` to be read by downstream auth middleware. - * - * **Fields obtained from the `googleIDToken`:** - * - * - `googleID` - * - `email` - * - `givenName` - * - `familyName` - * - `picture` (profile photo URL) - * - * > **The structure of Google JWT ID tokens is available here:** - * > https://developers.google.com/identity/gsi/web/reference/js-reference#credential - * - * @see https://developers.google.com/identity/gsi/web/guides/verify-google-id-token - */ -export const parseGoogleIDToken = mwAsyncCatchWrapper< - /** - * Since this mw creates user login args from the supplied Google ID token and adds - * them to the `req.body` object, an intersection is used for the type param here to - * tell downstream mw that `req.body` will include fields they require. Without this - * intersection, TS complains about the perceived `req.body` type mismatch. - */ - RestApiRequestBodyByPath["/auth/google-token"] & - RestApiRequestBodyByPath["/auth/login"] & - RestApiRequestBodyByPath["/auth/register"] ->(async (req, res, next) => { - // Since this mw is used on routes where auth via GoogleIDToken is optional, check if provided. - if (!req.body.googleIDToken) return next(); - - // Parse the google ID token: - const { _isValid, email, googleID, profile } = await parseGoogleOAuth2IDToken( - req.body.googleIDToken - ); - - // Add the fields to res.locals.googleIDTokenFields: - res.locals.googleIDTokenFields = { - _isValid, - email, - googleID, - profile, - }; - - // If not already present, add email to req.body for use by downstream mw (used by findUserByEmail) - if (!req.body.email) req.body.email = email; - - next(); -}); diff --git a/src/middleware/auth/queryUserItems.ts b/src/middleware/auth/queryUserItems.ts deleted file mode 100644 index 081a5557..00000000 --- a/src/middleware/auth/queryUserItems.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { safeJsonStringify } from "@nerdware/ts-type-safety-utils"; -import { usersCache } from "@/lib/cache/usersCache.js"; -import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; -import { Contact } from "@/models/Contact/Contact.js"; -import { Invoice } from "@/models/Invoice/Invoice.js"; -import { UserStripeConnectAccount } from "@/models/UserStripeConnectAccount/UserStripeConnectAccount.js"; -import { UserSubscription } from "@/models/UserSubscription/UserSubscription.js"; -import { WorkOrder } from "@/models/WorkOrder/WorkOrder.js"; -import { skTypeGuards } from "@/models/_common/skTypeGuards.js"; -import { ddbTable } from "@/models/ddbTable.js"; -import { AuthError } from "@/utils/httpErrors.js"; -import { logger } from "@/utils/logger.js"; -import type { ContactItem } from "@/models/Contact/Contact.js"; -import type { InvoiceItem } from "@/models/Invoice/Invoice.js"; -import type { UserItem } from "@/models/User/User.js"; -import type { UserStripeConnectAccountItem } from "@/models/UserStripeConnectAccount/UserStripeConnectAccount.js"; -import type { UserSubscriptionItem } from "@/models/UserSubscription/UserSubscription.js"; -import type { WorkOrderItem } from "@/models/WorkOrder/WorkOrder.js"; - -/** - * This middleware function fetches/pre-fetches the following types of User items: - * - * - Subscription(s) - used for authentication and authorization - * - StripeConnectAccount - used for authentication and authorization - * - Work Orders - pre-fetched for the user's dashboard - * - Invoices - pre-fetched for the user's dashboard - * - Contacts - pre-fetched for the user's dashboard - * - * ### Items Formatted for the GQL Client Cache - * On the client-side, these pre-fetched items are written into the Apollo client cache - * by a request handler, and are therefore expected to be in the shape specified by the - * GQL schema typedefs. This MW formats the items accordingly. - * - * ### Edge Case: _User not found_ - * If the User item is not found, this MW throws a 401 AuthError. This is unlikely to - * occur in prod, since the client would have to have sent a valid JWT auth token without - * existing in the db, but this *can* occur during development whenever a new empty - * DDB-local table is instantiated and the client has retained an auth token from previous - * interactions with the API. - */ -export const queryUserItems = mwAsyncCatchWrapper(async (req, res, next) => { - if (!res.locals?.authenticatedUser) return next(new AuthError("User not found")); - - // We want to retrieve items of multiple types, so we don't use a Model-instance here. - const response = await ddbTable.ddbClient.query({ - TableName: ddbTable.tableName, - KeyConditionExpression: `pk = :userID AND sk BETWEEN :skStart AND :skEnd`, - ExpressionAttributeValues: { - ":userID": res.locals.authenticatedUser.id, - ":skStart": `#DATA#${res.locals.authenticatedUser.id}`, - ":skEnd": "~", - // In utf8 byte order, tilde comes after numbers, upper+lowercase letters, #, and $. - }, - Limit: 100, // <-- ensures users with many items don't experience a delayed response - }); - - const items = response?.Items; - - // If no items were found, throw AuthError (see above jsdoc for details on this dev-env edge case) - if (!Array.isArray(items) || items.length === 0) throw new AuthError("User does not exist"); - - // Organize and format the items - const { user, subscription, stripeConnectAccount, workOrders, invoices, contacts } = items.reduce( - ( - accum: { - user: UserItem | null; - subscription: UserSubscriptionItem | null; - stripeConnectAccount: UserStripeConnectAccountItem | null; - workOrders: Array; - invoices: Array; - contacts: Array; - }, - current - ) => { - // Use type-guards to determine the type of the current item, and add it to the appropriate accum field - if (skTypeGuards.isContact(current)) accum.contacts.push(current); - else if (skTypeGuards.isInvoice(current)) accum.invoices.push(current); - else if (skTypeGuards.isUser(current)) accum.user = current; - else if (skTypeGuards.isUserSubscription(current)) accum.subscription = current; - else if (skTypeGuards.isUserStripeConnectAccount(current)) accum.stripeConnectAccount = current; // prettier-ignore - else if (skTypeGuards.isWorkOrder(current)) accum.workOrders.push(current); - else logger.warn(`[queryUserItems] The following ITEM was returned by the "queryUserItems" DDB query, but items of this type are not handled by the reducer. ${safeJsonStringify(current, null, 2)}`); // prettier-ignore - - return accum; - }, - { - user: null, - subscription: null, - stripeConnectAccount: null, - workOrders: [], - invoices: [], - contacts: [], - } - ); - - // If user was not found, throw AuthError (see above jsdoc for details on this dev-env edge case) - if (!user) throw new AuthError("User does not exist"); - - // Format the user's subscription object - if (subscription) { - const formattedSubItem = - UserSubscription.processItemAttributes.fromDB(subscription); - - res.locals.authenticatedUser.subscription = formattedSubItem; - res.locals.userSubscription = formattedSubItem; - } - - // Format the user's stripeConnectAccount object - if (stripeConnectAccount) { - res.locals.authenticatedUser.stripeConnectAccount = - UserStripeConnectAccount.processItemAttributes.fromDB( - stripeConnectAccount - ); - } - - /* Note: workOrders' and invoices' createdByUserID and assignedToUserID fields are converted - into createdBy and assignedTo objects with an "id" field, but no other createdBy/assignedTo - fields can be provided here without fetching additional data on the associated users/contacts - from either the db or usersCache. This middleware forgoes fetching the data since the client- - side Apollo cache already handles fetching additional data as needed (_if_ it's needed), and - fetching it here can delay auth request response times, especially if the authenticating user - has a large number of workOrders/invoices. */ - - res.locals.userItems = { - ...(workOrders.length > 0 && { - workOrders: workOrders.map((rawWorkOrder) => { - // Process workOrder from its raw internal shape: - const { createdByUserID, assignedToUserID, ...workOrderFields } = - WorkOrder.processItemAttributes.fromDB(rawWorkOrder); - - return { - // Fields which are nullable/optional in GQL schema must be provided, default to null: - category: null, - checklist: null, - dueDate: null, - entryContact: null, - entryContactPhone: null, - scheduledDateTime: null, - contractorNotes: null, - // workOrder values override above defaults: - ...workOrderFields, - // createdBy and assignedTo objects are formatted for the GQL client cache: - createdBy: { id: createdByUserID }, - assignedTo: assignedToUserID ? { id: assignedToUserID } : null, - }; - }), - }), - ...(invoices.length > 0 && { - invoices: invoices.map((rawInvoice) => { - // Process invoice from its raw internal shape: - const { createdByUserID, assignedToUserID, workOrderID, ...invoiceFields } = - Invoice.processItemAttributes.fromDB(rawInvoice); - - return { - // Fields which are nullable/optional in GQL schema must be provided, default to null: - stripePaymentIntentID: null, - // invoice values override above defaults: - ...invoiceFields, - // createdBy and assignedTo objects are formatted for the GQL client cache: - createdBy: { id: createdByUserID }, - assignedTo: { id: assignedToUserID }, - workOrder: workOrderID ? { id: workOrderID } : null, - }; - }), - }), - ...(contacts.length > 0 && { - contacts: contacts.map((rawContact) => { - // Process contact from its raw internal shape: - const contact = Contact.processItemAttributes.fromDB(rawContact); - - // Fetch some additional data from the usersCache - const { - email = "", // These defaults shouldn't be necessary, but are included for type-safety - phone = "", - profile = { displayName: contact.handle }, // displayName defaults to handle if n/a - } = usersCache.get(contact.handle) ?? {}; - - return { - id: contact.id, - handle: contact.handle, - email, - phone, - profile, - createdAt: contact.createdAt, - updatedAt: contact.updatedAt, - }; - }), - }), - }; - - next(); -}); diff --git a/src/middleware/auth/registerNewUser.ts b/src/middleware/auth/registerNewUser.ts deleted file mode 100644 index b93bea64..00000000 --- a/src/middleware/auth/registerNewUser.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; -import { Profile } from "@/models/Profile/Profile.js"; -import { User } from "@/models/User/User.js"; -import type { RestApiRequestBodyByPath } from "@/types/open-api.js"; -import type { UnionToIntersection } from "type-fest"; - -/** - * This middleware function creates a new User item in the DB. - */ -export const registerNewUser = mwAsyncCatchWrapper< - UnionToIntersection ->(async (req, res, next) => { - const { - body: { - handle, - email, - phone = null, - expoPushToken, // Only mobile-app logins will have this - password, // Only local logins will have this - }, - } = req; - - // For Google OAuth logins, get fields from the relevant res.locals object: - const { googleID, profile: profileParams } = res.locals.googleIDTokenFields ?? {}; - - // Set the authenticatedUser res.locals field used by `generateAuthToken` - res.locals.authenticatedUser = await User.createOne({ - handle, - email, - phone, - ...(expoPushToken && { expoPushToken }), - ...(profileParams && { profile: Profile.fromParams(profileParams) }), - password, - googleID, - }); - - /* Data from this endpoint is returned to the sending client, so there's - no need to limit visibility of attributes via "ProjectionExpression" in - the above call to User.create, which uses the DynamoDB PutItem command.*/ - - next(); -}); diff --git a/src/middleware/auth/shouldUserLoginExist.ts b/src/middleware/auth/shouldUserLoginExist.ts deleted file mode 100644 index 8e4d05ec..00000000 --- a/src/middleware/auth/shouldUserLoginExist.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { UserInputError, AuthError } from "@/utils/httpErrors.js"; -import type { RestApiRequestHandler } from "@/middleware/helpers.js"; -import type { RestApiRequestBodyByPath } from "@/types/open-api.js"; - -/** - * This middleware asserts that a UserLogin _**should not**_ exist on the `res.locals` object. - */ -export const userLoginShouldNotExist: RestApiRequestHandler< - RestApiRequestBodyByPath["/auth/register" | "/auth/login"] -> = (req, res, next) => { - if (res.locals?.user?.login) { - next(new UserInputError("An account already exists with the provided email address.")); - } - - next(); -}; - -/** - * This middleware asserts that a UserLogin _**should**_ exist, and the data for which has been - * attached to the `res.locals` object. - */ -export const userLoginShouldExist: RestApiRequestHandler< - RestApiRequestBodyByPath["/auth/register" | "/auth/login"] -> = (req, res, next) => { - if (!res.locals?.user?.login) { - next(new AuthError("User not found.")); - } - - next(); -}; diff --git a/src/middleware/auth/validateGqlReqContext.ts b/src/middleware/auth/validateGqlReqContext.ts deleted file mode 100644 index ab74c260..00000000 --- a/src/middleware/auth/validateGqlReqContext.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { UserSubscription } from "@/models/UserSubscription/UserSubscription.js"; -import { ENV } from "@/server/env"; -import { AuthToken, type FixitApiAuthTokenPayload } from "@/utils/AuthToken.js"; -import { GqlAuthError, GqlPaymentRequiredError } from "@/utils/httpErrors.js"; -import type { ApolloServerResolverContext } from "@/apolloServer.js"; -import type { Request } from "express"; - -/** - * This middleware validates and authenticates requests on the /api route, and ensures that - * no request reaches any GQL resolver unless the following are all true: - * - * 1. The request contains a valid unexpired AuthToken which was signed by the relevant key. - * 2. The AuthToken payload contains required User information. - * 3. The User's subscription is both active and unexpired. - * - * If all authentication criteria are met, the User's information is attached to the GQL - * resolvers' context object. - * - * @returns The context object made available to all GQL resolvers. - */ -const validateGqlRequest = async ({ - req, -}: { - req: Request; -}): Promise => { - // Authenticate the user - const tokenPayload = await AuthToken.getValidatedRequestAuthTokenPayload(req).catch(() => { - /* If err, re-throw as Apollo 401 auth error. By default, errors thrown from - apollo context-init fn return to client with http status code 500, so an "http" - extension is added here to ensure the status code is properly set to 401. */ - throw new GqlAuthError("Authentication required"); - }); - - // Ensure the User's subscription is active and not expired - try { - UserSubscription.validateExisting(tokenPayload.subscription); - } catch (err) { - // If err, re-throw as GQL 402 error - throw new GqlPaymentRequiredError("Payment required"); - } - - return { - ...req, - user: tokenPayload as FixitApiAuthTokenPayload, - }; -}; - -/** - * This function tests for whether or not an incoming request is a MANUAL - * schema-update introspection query, submitted either via the Rover CLI or from - * Apollo Studio, with the former being the method usedfor updating the "staging" - * and "prod" variants in the CI pipeline. - * - * During development, however, the "current" variant of the Fixit schema is - * updated via PUSH event at server startup using the APOLLO_KEY and - * APOLLO_GRAPH_REF env vars. - */ -const isIntrospectionQuery = ({ - req, -}: { - req: Request; -}): boolean => { - // Manual schema-update introspection queries submitted from Apollo Studio: - const isIntrospectionQueryFromApolloStudio = - req.get("origin") === "https://studio.apollographql.com" && - !!req?.body?.query && - req.body.query.includes("query IntrospectionQuery"); - - // Manual schema-update introspection queries submitted via Rover CLI: - const isIntrospectionQueryFromRoverCLI = - ((req.get("user-agent") ?? "").startsWith("rover") || req.hostname === "localhost") && - !!req?.body?.operationName && - req.body.operationName === "GraphIntrospectQuery"; - - return isIntrospectionQueryFromApolloStudio || isIntrospectionQueryFromRoverCLI; -}; - -/** - * This MW is used to create "context" within ApolloServer. - * - Permits ApolloStudio and ApolloSandbox introspection queries in the dev env. - */ -export const validateGqlReqContext = !ENV.NODE_ENV.startsWith("dev") - ? validateGqlRequest - : async ({ req }: { req: Request }) => { - return isIntrospectionQuery({ req }) - ? (req as ApolloServerResolverContext) - : await validateGqlRequest({ req }); - }; diff --git a/src/middleware/auth/validateLogin.ts b/src/middleware/auth/validateLogin.ts deleted file mode 100644 index 4404a65c..00000000 --- a/src/middleware/auth/validateLogin.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; -import { AuthError, InternalServerError } from "@/utils/httpErrors.js"; -import { passwordHasher } from "@/utils/passwordHasher.js"; -import type { CombineUnionOfObjects } from "@/types/helpers.js"; -import type { RestApiRequestBodyByPath } from "@/types/open-api.js"; - -/** - * This middleware validates User's Login objects. - * - * - If the User's login type is `"LOCAL"`, it compares the provided password - * against the passwordHash stored in the db. - * - * - If the User's login type is `"GOOGLE_OAUTH"`, it checks the value of - * `res.locals.googleIDTokenFields?._isValid`, which is set by the `parseGoogleIDToken` - * middleware. - * - * If it's invalid, an AuthError is thrown. - */ -export const validateLogin = mwAsyncCatchWrapper< - CombineUnionOfObjects ->(async (req, res, next) => { - const userItem = res.locals?.user; - - if (!userItem) return next(new AuthError("User not found")); - - // LOCAL LOGIN — validate password - if (userItem.login.type === "LOCAL") { - // Ensure password was provided - if (!req.body?.password) return next(new AuthError("Password is required")); - - const isValidPassword = await passwordHasher.validate( - req.body.password, - userItem.login.passwordHash - ); - - if (!isValidPassword) next(new AuthError("Invalid email or password")); - - /* Note: res.locals.user does not have `subscription`/`stripeConnectAccount` fields. - For `generateAuthToken`, these fields are obtained from the `queryUserItems` mw. */ - res.locals.authenticatedUser = userItem; - - // GOOGLE_OAUTH LOGIN — check res.locals.googleIDTokenFields._isValid - } else if (userItem.login.type === "GOOGLE_OAUTH") { - // The parseGoogleIDToken mw provides this res.locals field, check `_isValid`. The - // field should always be true here, else the fn would throw, but it provides clarity. - if (res.locals.googleIDTokenFields?._isValid) res.locals.authenticatedUser = userItem; - } else { - next(new InternalServerError("Invalid login")); - } - - next(); -}); diff --git a/src/middleware/security/cors.ts b/src/middleware/cors.ts similarity index 50% rename from src/middleware/security/cors.ts rename to src/middleware/cors.ts index e452f6bb..6c613699 100644 --- a/src/middleware/security/cors.ts +++ b/src/middleware/cors.ts @@ -2,25 +2,27 @@ import cors, { type CorsOptions } from "cors"; import { ENV } from "@/server/env"; const corsOptions: CorsOptions = { + credentials: true, + origin: [ - "https://studio.apollographql.com", - ...(/^(dev|test)/.test(ENV.NODE_ENV) - ? [/localhost/] - : [/^https:\/\/(www\.)?((demo|staging)\.)?gofixit.app/]), + ENV.WEB_CLIENT.URL, + "https://studio.apollographql.com", // Apollo Studio origin for introspection ], + allowedHeaders: [ "Content-Type", "Authorization", - // Sentry tracing http headers: + // Sentry tracing base http headers "sentry-trace", "baggage", - // Apollo GraphQL http headers: + // Apollo GraphQL base http headers "apollographql-client-name", "apollographql-client-version", - // Enable ApolloServerPluginInlineTrace + "Apollo-Require-Preflight", + // Apollo GraphQL header for mobile clients + "X-Apollo-Operation-Name", + // Apollo GraphQL header to enable ApolloServerPluginInlineTrace "apollo-federation-include-trace", - // Permit access to Apollo Studio queries - "Apollo-Studio-Auth-Token", ], }; diff --git a/src/middleware/errorHandler.ts b/src/middleware/errorHandler.ts new file mode 100644 index 00000000..6bda83b9 --- /dev/null +++ b/src/middleware/errorHandler.ts @@ -0,0 +1,50 @@ +import { getTypeSafeError } from "@nerdware/ts-type-safety-utils"; +import { ENV } from "@/server/env"; +import { logger } from "@/utils/logger.js"; +import type { CombineUnionOfObjects } from "@/types/helpers.js"; +import type { HttpError } from "@/utils/httpErrors.js"; +import type { ErrorRequestHandler } from "express"; + +const DEFAULT_ERROR_MESSAGE = "An unexpected problem occurred"; + +/** + * This function serves as the fallback Express error-handler. + * + * 1. Parses the provided error object (`originalError`) + * + * 2. Logs pertinent info if the error either + * - **(a)** has an http `statusCode` of `5xx`, or + * - **(b)** does not have a `statusCode` property + * + * 3. Sends a JSON error-response to the client + * > _**In prod, `5xx` error messages are masked**_ + */ +export const errorHandler: ErrorRequestHandler< + Record, + unknown, + Record +> = (originalError: unknown, req, res, next) => { + // Parse the originalError param + const error = getTypeSafeError(originalError, { fallBackErrMsg: DEFAULT_ERROR_MESSAGE }); + + const { statusCode: errorStatusCode = 500 } = error as CombineUnionOfObjects; + let { message: errorMessage } = error; + + if (errorStatusCode >= 500) { + // Destructure req to get pertinent info + const { baseUrl, body, headers, ips, method, originalUrl, path } = req; + logger.error( + { originalError, req: { baseUrl, body, headers, ips, method, originalUrl, path } }, + `SERVER ERROR on route "${req.originalUrl}"` + ); + + // Mask 5xx error messages in production + if (ENV.IS_PROD) errorMessage = DEFAULT_ERROR_MESSAGE; + } + + // If streaming back to the client has already been initiated, use Express's built-in default-error-handler. + if (res.headersSent) next(originalError); + + // Send JSON response to client + res.status(errorStatusCode).json({ error: errorMessage }); +}; diff --git a/src/middleware/errorHandlers/errorHandler.ts b/src/middleware/errorHandlers/errorHandler.ts deleted file mode 100644 index 84594998..00000000 --- a/src/middleware/errorHandlers/errorHandler.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { getTypeSafeError, safeJsonStringify } from "@nerdware/ts-type-safety-utils"; -import { ENV } from "@/server/env"; -import { logger } from "@/utils/logger.js"; -import type { HttpErrorInterface } from "@/utils/httpErrors.js"; -import type { ErrorRequestHandler } from "express"; - -/** - * This is the default error-handling middleware which captures errors and sends a - * JSON response to the client. - */ -export const errorHandler: ErrorRequestHandler = (err: unknown, req, res, next) => { - const error = getTypeSafeError(err); - - const errorStatusCode = (error as HttpErrorInterface)?.statusCode || 500; - - if (errorStatusCode >= 500) { - logger.error(`[SERVER ERROR] On route "${req.originalUrl}": ${safeJsonStringify(err)}`); - } - - // If streaming back to the client has already been initiated, just delegate to the default built-in error handler. - if (res.headersSent) return next(err); - - // Send JSON response to client - res.status(errorStatusCode).json({ - error: - errorStatusCode >= 500 && ENV.IS_PROD // mask 5xx error messages in production - ? MASKED_ERROR_MESSAGE - : error.message || MASKED_ERROR_MESSAGE, - }); -}; - -const MASKED_ERROR_MESSAGE = "An unexpected error occurred"; diff --git a/src/middleware/errorHandlers/index.ts b/src/middleware/errorHandlers/index.ts deleted file mode 100644 index c95f16de..00000000 --- a/src/middleware/errorHandlers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./errorHandler.js"; -export * from "./handle404.js"; diff --git a/src/middleware/errorHandlers/handle404.ts b/src/middleware/handle404.ts similarity index 72% rename from src/middleware/errorHandlers/handle404.ts rename to src/middleware/handle404.ts index 7536e8a3..4a883fdb 100644 --- a/src/middleware/errorHandlers/handle404.ts +++ b/src/middleware/handle404.ts @@ -1,11 +1,11 @@ import { NotFoundError } from "@/utils/httpErrors.js"; import { logger } from "@/utils/logger.js"; -import type { RestApiRequestHandler } from "@/middleware/helpers.js"; +import type { RequestHandler } from "express"; /** * This middleware function captures all 404 errors and throws a NotFoundError. */ -export const handle404: RestApiRequestHandler = ({ originalUrl }) => { +export const handle404: RequestHandler = ({ originalUrl }) => { logger.error(`Request received for non-existent path, req.originalUrl: "${originalUrl}"`); throw new NotFoundError(`Unable to find the requested resource at "${originalUrl}"`); }; diff --git a/src/middleware/helpers.ts b/src/middleware/helpers.ts deleted file mode 100644 index 74e2bdfd..00000000 --- a/src/middleware/helpers.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { - hasKey, - isString, - isSafeInteger, - isFunction, - isPlainObject, - isArray, - isBoolean, - getErrorMessage, -} from "@nerdware/ts-type-safety-utils"; -import { UserInputError } from "@/utils/httpErrors.js"; -import type { RestApiLocals } from "@/types/express.js"; -import type { AllRestApiResponses } from "@/types/open-api.js"; -import type { RequestHandler } from "express"; -import type { JsonPrimitive, SetRequired, SetReturnType } from "type-fest"; - -/** - * Generic catch wrapper for async middleware functions. - */ -export const mwAsyncCatchWrapper = < - ReqBody extends Record = Record, ->( - asyncMiddlewareFn: SetReturnType, Promise> -): RestApiRequestHandler => { - return (req, res, next) => { - asyncMiddlewareFn(req, res, next).catch(next); - }; -}; - -/** - * This type wraps the Express `RequestHandler` type with app global defaults. - * Provide the `ReqBody` type param to specify a `req.body` object type. - * - * **Note:** The `P` and `ReqQuery` type params are set to `never` since this app currently - * does not use route or query params. - */ -export type RestApiRequestHandler< - ReqBody extends Record = Record, -> = RequestHandler< - never, // route params (req.params) - AllRestApiResponses, - ReqBody, - never, // query params (req.query) - RestApiLocals ->; - -/** - * This function provides middleware for validating and sanitizing request body parameters. - * - * @param reqBodySchema - The schema object that defines the expected structure and validation rules for the request body. - * @returns A middleware function that can be used in an Express route. - * @throws UserInputError if any validation fails. - * - * @example - * import { sanitizeAndValidateRequestBody } from "@/middleware/helpers.js"; - * - * const requestBodySchema = { - * name: { type: "string", required: true, sanitize: (value) => value.trim() }, - * age: { type: "number", required: true, validate: (value) => value >= 18 }, - * email: { type: "string", required: false, sanitize: (value) => sanitizeUtil.email(value) }, - * }; - * - * app.post("/api/user", sanitizeAndValidateRequestBody({ requestBodySchema }), (req, res) => { - * // Process the validated and sanitized request body - * }); - */ -export const sanitizeAndValidateRequestBody = ({ - requestBodySchema, - validateRequestBody, -}: SanitizeAndValidateReqBodyParams): RequestHandler => { - // Validate each field config, add a type-validator function based on the "type" - const reqBodySchemaWithTypeValidator = Object.fromEntries( - Object.entries(requestBodySchema).map( - ([key, { type: fieldType, nullable, sanitize, ...fieldConfig }]) => { - // Obtain the type-validator from the `TYPE_VALIDATORS` dict: - const isValidType = TYPE_VALIDATORS?.[fieldType]; - // If isValidType is undefined, then fieldType is not valid, throw an error - if (!isValidType) throw new Error(`Invalid "type" for field "${key}": "${fieldType}"`); - // If fieldType is string/object/array, ensure a sanitize function is provided - if (["string", "object", "array"].includes(fieldType) && !isFunction(sanitize)) - throw new Error(`Field "${key}" is missing a "sanitize" function`); - // Return the field config with the type-validator function - return [key, { ...fieldConfig, type: fieldType, nullable, sanitize, isValidType }]; - } - ) - ) as unknown as { - [Key in keyof Schema]: Schema[Key] & { isValidType: (arg: unknown) => boolean }; - }; - - return (req, res, next) => { - // Ensure the request body is valid - if (!hasKey(req as Record, "body") || !isPlainObject(req.body)) { - throw new UserInputError("Invalid request body"); - } - - const reqBodyFields = {} as { [Key in keyof Schema]: unknown }; - - try { - for (const key in requestBodySchema) { - // Destructure the field config - const { required, isValidType, sanitize, validate } = reqBodySchemaWithTypeValidator[key]; - - // Get `nullable`, default to the opposite of `required`: - const { nullable = !required } = reqBodySchemaWithTypeValidator[key]; - - // Check is the field is defined in req.body - if (!hasKey(req.body, key) || req.body[key] === undefined) { - // If not defined, throw error if field is required, else continue - if (required === true) throw new UserInputError(`Missing required field: "${key}"`); - else continue; - } - - // The field is defined, get from req.body using `let` so `sanitize` can overwrite it - let fieldValue = req.body[key]; - - // Check if fieldValue is null - if (fieldValue === null) { - // If fieldValue is null, and the field is not nullable, throw error - if (nullable !== true) throw new UserInputError(`Invalid value for field: "${key}"`); - } else { - // If fieldValue is not null, sanitize and validate the value - if (!isValidType(fieldValue)) - throw new UserInputError(`Invalid value for field: "${key}"`); - - if (isFunction(sanitize)) fieldValue = sanitize(fieldValue as any); - - if (isFunction(validate) && (validate as (v: unknown) => boolean)(fieldValue) === false) { - throw new UserInputError(`Invalid value for field: "${key}"`); - } - } - - // The field is valid - add to reqBodyFields - reqBodyFields[key] = fieldValue; - } - - // Validate the entire request body if a validateReqBody function was provided - if (isFunction(validateRequestBody) && validateRequestBody(reqBodyFields) === false) { - throw new UserInputError("Invalid request body"); - } - - // Update req.body with sanitized and validated values - req.body = reqBodyFields; - next(); - } catch (error) { - // Re-throw as UserInputError to ensure 400 status code is attached - next(new UserInputError(getErrorMessage(error))); - } - }; -}; - -/** Dictionary of type validators for request body fields. @internal */ -const TYPE_VALIDATORS = { - string: isString, - number: isSafeInteger, - boolean: isBoolean, - object: isPlainObject, - array: isArray, -} as const satisfies Record boolean>; - -/** Params for {@link sanitizeAndValidateReqBody}. */ -export type SanitizeAndValidateReqBodyParams = { - /** - * A map of `req.body` fields to {@link RequestBodyFieldConfig} objects defining field-level - * sanitization and validation for each respective field. - */ - requestBodySchema: Schema; - /** - * A validation function for `req.body` as a whole which is called with the entire `req.body` - * object after all field-level validation and sanitization has occured. Use this to implement - * multi-field validation logic. If the function returns `false`, an error will be thrown with - * a generic _"invalid request body"_ error message. You can also throw an error from this fn - * to provide your own error message. If the function neither throws nor returns `false`, the - * value will be considered valid. - */ - validateRequestBody?: RequestBodyValidatorFn; -}; - -export type RequestBodyValidatorFn = (reqBody: Record) => boolean | void; - -/** - * A map of `req.body` fields to {@link RequestBodyFieldConfig} objects defining field-level - * sanitization and validation for each respective field. - */ -export interface RequestBodyFieldsSchema { - readonly [key: string]: - | SetRequired, "sanitize"> - | SetRequired, "sanitize"> - | SetRequired, "sanitize"> - | RequestBodyFieldConfig<"number"> - | RequestBodyFieldConfig<"boolean">; -} - -/** Config object for field-level sanitization and validation for fields in `req.body`. */ -export interface RequestBodyFieldConfig { - /** The type of an individual field; currently types are limited to valid JSON value types. */ - type: T; - /** If `true`, an error will be thrown if the field is not present. */ - required: boolean; - /** If `true`, an error will not be thrown if the field's value is `null`. */ - nullable?: boolean; - /** - * A function to strip client-provided values of undesirable characters/patterns. - * This function is required for "string", "object", and "array" types. - * @param value - The client-provided value to sanitize (the type will have already been checked) - * @returns The sanitized value. - */ - sanitize?: T extends "string" | "object" | "array" ? RequestBodyFieldSanitizerFn : never; - /** - * A function which validates the client-provided value for the field. If the function returns - * `false`, an error will be thrown with a generic _"invalid value for x"_ error message. You - * can also throw an error from this function to provide your own error message. If the function - * neither throws nor returns `false`, the value will be considered valid. - * @param value - The client-provided value to validate (the type will have already been checked) - * @returns true or undefined if the client-provided value is valid for the field, false otherwise. - */ - validate?: RequestBodyFieldValidatorFn; -} - -/** A sanitizer function for a {@link RequestBodyFieldConfig}. */ -export type RequestBodyFieldSanitizerFn = ( - value: StringLiteralToType -) => StringLiteralToType; - -/** A validator function for a {@link RequestBodyFieldConfig}. */ -export type RequestBodyFieldValidatorFn = ( - value: StringLiteralToType -) => boolean | void; - -/** Generic util which converts a {@link JsonTypeStringLiteral} to its corresponding type. */ -type StringLiteralToType = T extends "string" - ? string - : T extends "number" - ? number - : T extends "boolean" - ? boolean - : T extends "object" - ? Record | Array> - : T extends "array" - ? Array | Array> - : never; - -/** Union of JSON-type string literals for implemented types. */ -type JsonTypeStringLiteral = "string" | "number" | "boolean" | "object" | "array"; diff --git a/src/middleware/index.ts b/src/middleware/index.ts index c2734fc1..ea24eaa8 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -1,8 +1,10 @@ -export * from "./auth"; -export * from "./errorHandlers"; -export * from "./pushNotifications"; -export * from "./security"; -export * from "./stripeConnect"; -export * from "./stripeSubscriptions"; -export * from "./stripeWebhooks"; -export * from "./util-mw"; +// Security-related middleware +export * from "./cors.js"; +export * from "./setSecureHttpHeaders.js"; + +// Error-handling middleware +export * from "./errorHandler.js"; +export * from "./handle404.js"; + +// Util middleware +export * from "./logReqReceived.js"; diff --git a/src/middleware/util-mw/logReqReceived.ts b/src/middleware/logReqReceived.ts similarity index 75% rename from src/middleware/util-mw/logReqReceived.ts rename to src/middleware/logReqReceived.ts index c50c46fd..664b328f 100644 --- a/src/middleware/util-mw/logReqReceived.ts +++ b/src/middleware/logReqReceived.ts @@ -1,7 +1,6 @@ import { isString } from "@nerdware/ts-type-safety-utils"; import { logger } from "@/utils/logger.js"; -import type { RestApiRequestHandler } from "@/middleware/helpers.js"; -import type { Request } from "express"; +import type { Request, RequestHandler } from "express"; /** * This middleware logs GraphQL and REST requests. @@ -12,12 +11,15 @@ import type { Request } from "express"; * * - Requests to `/api/admin/healthcheck` are not logged. */ -export const logReqReceived: RestApiRequestHandler = (req, res, next) => { +export const logReqReceived: RequestHandler< + Record, + unknown, + Record | undefined +> = (req, res, next) => { if (req.originalUrl === "/api") { // Only log GQL /api requests if req.body.operationName exists - if (isString(req.body?.operationName)) { + if (isString(req.body?.operationName)) logger.gql(getReqLogMsg(req, `OPERATION ${req.body.operationName}`)); - } } else if (req.originalUrl !== "/api/admin/healthcheck") { logger.server(getReqLogMsg(req, `PATH ${req.originalUrl}`)); } @@ -26,5 +28,5 @@ export const logReqReceived: RestApiRequestHandler = (req, res, next) => { }; const getReqLogMsg = (req: Request, reqInfoStr: string) => { - return `request received: ${reqInfoStr.padEnd(36, " ")} FROM ${req.ip}`; + return `request received: ${reqInfoStr.padEnd(36, " ")} FROM ${req.ip ?? "-UNKNOWN-"}`; }; diff --git a/src/middleware/pushNotifications/index.ts b/src/middleware/pushNotifications/index.ts deleted file mode 100644 index 08d34152..00000000 --- a/src/middleware/pushNotifications/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./updateExpoPushToken.js"; diff --git a/src/middleware/pushNotifications/updateExpoPushToken.ts b/src/middleware/pushNotifications/updateExpoPushToken.ts deleted file mode 100644 index 8d59b54b..00000000 --- a/src/middleware/pushNotifications/updateExpoPushToken.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; -import { User } from "@/models/User/User.js"; -import { AuthError } from "@/utils/httpErrors.js"; -import type { RestApiRequestBodyByPath } from "@/types/open-api.js"; - -/** - * On the Fixit mobile app, if the user's ExpoPushToken has changed/expired - * since the last login, the app will send the new one along with the request. - * This middleware checks the request body for `expoPushToken`, and updates - * the value in the database if one was provided. - */ -export const updateExpoPushToken = mwAsyncCatchWrapper< - RestApiRequestBodyByPath["/auth/login" | "/auth/token"] ->(async (req, res, next) => { - const { authenticatedUser } = res.locals; - - if (!authenticatedUser) return next(new AuthError("User not found")); - - const { expoPushToken } = req.body; - - if (expoPushToken) { - await User.updateItem({ id: authenticatedUser.id }, { update: { expoPushToken } }); - } - - next(); -}); diff --git a/src/middleware/security/index.ts b/src/middleware/security/index.ts deleted file mode 100644 index 973a6ec5..00000000 --- a/src/middleware/security/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./cors.js"; -export * from "./setSecureHttpHeaders.js"; diff --git a/src/middleware/security/setSecureHttpHeaders.ts b/src/middleware/security/setSecureHttpHeaders.ts deleted file mode 100644 index 3463015b..00000000 --- a/src/middleware/security/setSecureHttpHeaders.ts +++ /dev/null @@ -1,71 +0,0 @@ -import helmet from "helmet"; -import { ENV } from "@/server/env"; -import type { RequestHandler } from "express"; - -/** - * FIXIT CSP DIRECTIVE SOURCES - */ -const FIXIT_SOURCES = { - WEB: ["'self'", "https://gofixit.app", "https://*.gofixit.app"], - API: ["'self'", "https://gofixit.app/", "https://*.gofixit.app/"], -} as const; - -/** - * Security Middleware: `setSecureHttpHeaders` - * - * This [Helmet][helmet-url] config produces a secure `Content-Security-Policy` which serves as a - * strong mitigation against [cross-site scripting attacks][google-xss-info]. The resultant CSP has - * been evaluated using https://csp-evaluator.withgoogle.com/. - * - * These directives reflect a combination of sources: - * - * - HelmetJS defaults - * - [Stripe requirements][stripe-csp] - * - [Google API requirements][google-api-csp] - * - Fixit-related sources - * - * [helmet-url]: https://helmetjs.github.io/ - * [google-xss-info]: https://www.google.com/about/appsecurity/learning/xss/ - * [stripe-csp]: https://stripe.com/docs/security/guide#content-security-policy - * [google-api-csp]: https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid#cross_origin_opener_policy - */ -const helmetMW = helmet({ - contentSecurityPolicy: { - useDefaults: true, - directives: { - "default-src": [...FIXIT_SOURCES.WEB, "https://accounts.google.com/gsi/"], - "base-uri": "'self'", - "connect-src": [...FIXIT_SOURCES.API, "https://*.ingest.sentry.io/", "https://accounts.google.com/gsi/", "https://api.stripe.com"], // prettier-ignore - "font-src": ["'self'", "https:", "data:"], - "form-action": [...FIXIT_SOURCES.API], - "frame-ancestors": [...FIXIT_SOURCES.WEB], - "frame-src": ["https://accounts.google.com/gsi/", "https://js.stripe.com", "https://hooks.stripe.com"], // prettier-ignore - "img-src": [...FIXIT_SOURCES.WEB, "data:", "blob:"], - "object-src": "'none'", - "report-to": "fixit-security", - "report-uri": `${ENV.CONFIG.API_FULL_URL}/admin/csp-violation`, - "script-src": [...FIXIT_SOURCES.WEB, "https://accounts.google.com/gsi/client", "https://js.stripe.com"], // prettier-ignore - "script-src-attr": "'none'", - "style-src": [...FIXIT_SOURCES.WEB, "https://accounts.google.com/gsi/style", "https:", "'unsafe-inline'"], // prettier-ignore - "upgrade-insecure-requests": [], - }, - }, -}); - -/** - * `setSecureHttpHeaders` uses Helmet to set security-related HTTP headers. - */ -export const setSecureHttpHeaders: RequestHandler = (req, res, next) => { - res.set({ - "Cache-Control": "no-store", - "Report-To": REPORT_TO_HTTP_HEADER_VALUE_JSON, - }); - - helmetMW(req, res, next); -}; - -const REPORT_TO_HTTP_HEADER_VALUE_JSON = JSON.stringify({ - group: "fixit-security", - max_age: 10886400, - url: `${ENV.CONFIG.API_FULL_URL}/admin/csp-violation`, -}); diff --git a/src/middleware/setSecureHttpHeaders.ts b/src/middleware/setSecureHttpHeaders.ts new file mode 100644 index 00000000..baba4e90 --- /dev/null +++ b/src/middleware/setSecureHttpHeaders.ts @@ -0,0 +1,70 @@ +import helmet from "helmet"; +import { ENV } from "@/server/env"; +import type { RequestHandler } from "express"; + +/** + * This file contains Express middleware that sets HTTP headers with the intent + * of hardening the application's security posture, as well as approximating the + * headers actually returned to clients in deployed environments. In practice, + * live deployments operate behind various infrastructure components that override + * request+response headers. + */ + +// CSP DIRECTIVE SOURCES +const SELF = "'self'"; +const API_CSP_SOURCE = `${ENV.CONFIG.API_BASE_URL}/`; +const API_CSP_REPORT_URI = `${ENV.CONFIG.API_BASE_URL}/admin/csp-violation`; +const GOOGLE_GSI = "https://accounts.google.com/gsi/"; +const GOOGLE_GSI_CLIENT = "https://accounts.google.com/gsi/client"; +const GOOGLE_GSI_STYLE = "https://accounts.google.com/gsi/style"; +const STRIPE_API = "https://api.stripe.com"; +const STRIPE_HOOKS = "https://hooks.stripe.com"; +const STRIPE_JS = "https://js.stripe.com"; +const SENTRY_INGEST = "https://*.ingest.sentry.io/"; + +/** + * This [Helmet][helmet-url] config produces a CSP that reflects a variety of sources: + * + * - Fixit-related sources + * - [Stripe requirements][stripe-csp] + * - [Google API requirements][google-api-csp] + * - Sentry reporting + * + * [helmet-url]: https://helmetjs.github.io/ + * [stripe-csp]: https://stripe.com/docs/security/guide#content-security-policy + * [google-api-csp]: https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid#cross_origin_opener_policy + */ +const helmetMW = helmet({ + contentSecurityPolicy: { + useDefaults: true, + directives: { + "default-src": [SELF, ENV.WEB_CLIENT.URL, GOOGLE_GSI], + "base-uri": SELF, + "connect-src": [SELF, API_CSP_SOURCE, SENTRY_INGEST, GOOGLE_GSI, STRIPE_API], + "font-src": [SELF, "https:", "data:"], + "form-action": [SELF, API_CSP_SOURCE], + "frame-ancestors": [SELF, ENV.WEB_CLIENT.URL], + "frame-src": [GOOGLE_GSI, STRIPE_JS, STRIPE_HOOKS], + "img-src": [ENV.WEB_CLIENT.URL, "data:", "blob:"], + "object-src": "'none'", + "report-to": "fixit-security", + "report-uri": API_CSP_REPORT_URI, + "script-src": [SELF, ENV.WEB_CLIENT.URL, GOOGLE_GSI_CLIENT, STRIPE_JS], + "script-src-attr": "'none'", + "style-src": [SELF, ENV.WEB_CLIENT.URL, GOOGLE_GSI_STYLE, "https:", "'unsafe-inline'"], + "upgrade-insecure-requests": [], + }, + }, +}); + +/** + * `setSecureHttpHeaders` uses Helmet to set security-related HTTP headers. + */ +export const setSecureHttpHeaders: RequestHandler = (req, res, next) => { + res.set({ + "Cache-Control": "no-store", + "Report-To": `{"group":"fixit-security","max_age":10886400,"url":"${API_CSP_REPORT_URI}"}`, + }); + + helmetMW(req, res, next); +}; diff --git a/src/middleware/stripeConnect/checkOnboardingStatus.ts b/src/middleware/stripeConnect/checkOnboardingStatus.ts deleted file mode 100644 index 45410404..00000000 --- a/src/middleware/stripeConnect/checkOnboardingStatus.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { stripe } from "@/lib/stripe/stripeClient.js"; -import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; -import { UserStripeConnectAccount } from "@/models/UserStripeConnectAccount/UserStripeConnectAccount.js"; -import { AuthError, InternalServerError } from "@/utils/httpErrors.js"; - -/** - * Checks the status of the user's Stripe Connect account capabilities and updates the DB - * if the values are stale (`details_submitted`, `charges_enabled`, and `payouts_enabled`). - */ -export const checkOnboardingStatus = mwAsyncCatchWrapper(async (req, res, next) => { - if (!res.locals?.authenticatedUser) return next(new AuthError("User not found")); - - const { authenticatedUser } = res.locals; - - if (!authenticatedUser?.stripeConnectAccount) - return next(new InternalServerError("User's Stripe Connect account not found")); - - const { - id: userID, - stripeConnectAccount: { id, detailsSubmitted, chargesEnabled, payoutsEnabled }, - } = authenticatedUser; - - // prettier-ignore - const { details_submitted, charges_enabled, payouts_enabled } = await stripe.accounts.retrieve(id); - - // Update DB if values are stale - if ( - details_submitted !== !!detailsSubmitted || - charges_enabled !== !!chargesEnabled || - payouts_enabled !== !!payoutsEnabled - ) { - const updatedStripeConnectAccount = await UserStripeConnectAccount.updateItem( - { userID }, - { - update: { - detailsSubmitted: !!details_submitted, - chargesEnabled: !!charges_enabled, - payoutsEnabled: !!payouts_enabled, - }, - } - ); - - res.locals.authenticatedUser.stripeConnectAccount = { - ...authenticatedUser.stripeConnectAccount, - ...(updatedStripeConnectAccount ?? {}), - }; - } - - next(); -}); diff --git a/src/middleware/stripeConnect/createAccountLink.ts b/src/middleware/stripeConnect/createAccountLink.ts deleted file mode 100644 index 6e0a527f..00000000 --- a/src/middleware/stripeConnect/createAccountLink.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { stripe } from "@/lib/stripe/stripeClient.js"; -import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; -import type { RestApiRequestBodyByPath } from "@/types/open-api.js"; - -/** - * This middleware creates a Stripe ConnectAccount link for authenticated users. - */ -export const createAccountLink = mwAsyncCatchWrapper< - RestApiRequestBodyByPath["/connect/account-link"] ->(async (req, res, next) => { - const { authenticatedUser } = res.locals; - - if (!authenticatedUser) return next("User not found."); - if (!authenticatedUser?.stripeConnectAccount) - return next("User's Stripe Connect account not found."); - - const stripeLink = await stripe.accountLinks.create({ - account: authenticatedUser.stripeConnectAccount.id, - return_url: `${req.body.returnURL}?connect-return`, - refresh_url: `${req.body.returnURL}?connect-refresh`, - type: "account_onboarding", - }); - - res.json({ stripeLink: stripeLink.url }); -}); diff --git a/src/middleware/stripeConnect/createDashboardLink.ts b/src/middleware/stripeConnect/createDashboardLink.ts deleted file mode 100644 index e6181257..00000000 --- a/src/middleware/stripeConnect/createDashboardLink.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { stripe } from "@/lib/stripe/stripeClient.js"; -import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; -import { AuthError } from "@/utils/httpErrors.js"; - -/** - * This middleware creates a Stripe dashboard link for authenticated users. - * - * Example account-login-link response object: - * - * ```json - * { - * "object": "login_link", - * "created": 1495580507, - * "url": "https://stripe.com/express/Ln7FfnNpUcCU" - * } - * ``` - */ -export const createDashboardLink = mwAsyncCatchWrapper(async (req, res, next) => { - const { authenticatedUser } = res.locals; - - if (!authenticatedUser) return next(new AuthError("User not found")); - if (!authenticatedUser?.stripeConnectAccount) - return next("User's Stripe Connect account not found."); - - const stripeLink = await stripe.accounts.createLoginLink( - authenticatedUser.stripeConnectAccount.id - ); - - res.json({ stripeLink: stripeLink.url }); -}); diff --git a/src/middleware/stripeConnect/index.ts b/src/middleware/stripeConnect/index.ts deleted file mode 100644 index 2d0d78bd..00000000 --- a/src/middleware/stripeConnect/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./checkOnboardingStatus.js"; -export * from "./createAccountLink.js"; -export * from "./createDashboardLink.js"; diff --git a/src/middleware/stripeSubscriptions/checkPromoCode.ts b/src/middleware/stripeSubscriptions/checkPromoCode.ts deleted file mode 100644 index 397fad2b..00000000 --- a/src/middleware/stripeSubscriptions/checkPromoCode.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { promoCodesCache } from "@/lib/cache/promoCodesCache.js"; -import type { RestApiRequestHandler } from "@/middleware/helpers.js"; -import type { RestApiRequestBodyByPath } from "@/types/open-api.js"; - -/** - * This middlware serves as an endpoint which receives a `promoCode` string, and responds with - * information regarding the `promoCode`s validity and discount percentage (if valid/applicable). - */ -export const checkPromoCode: RestApiRequestHandler< - RestApiRequestBodyByPath["/subscriptions/check-promo-code"] -> = (req, res) => { - // Destructure req.body - const { promoCode: maybePromoCode } = req.body; - - const maybeDiscountPercentage = promoCodesCache.get(maybePromoCode)?.discount; - - res.json({ - promoCodeInfo: { - value: maybePromoCode, - isValidPromoCode: !!maybeDiscountPercentage, - ...(!!maybeDiscountPercentage && { discountPercentage: maybeDiscountPercentage }), - }, - }); -}; diff --git a/src/middleware/stripeSubscriptions/checkSubscriptionStatus.ts b/src/middleware/stripeSubscriptions/checkSubscriptionStatus.ts deleted file mode 100644 index 49dc40c3..00000000 --- a/src/middleware/stripeSubscriptions/checkSubscriptionStatus.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { stripe } from "@/lib/stripe/stripeClient.js"; -import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; -import { UserSubscription } from "@/models/UserSubscription/UserSubscription.js"; -import { AuthError, InternalServerError } from "@/utils/httpErrors.js"; - -/** - * This middleware checks if the User is authenticated, and if so, queries Stripe for - * up-to-date subscription info. It then checks if the subscription's `status` and - * `currentPeriodEnd` details from Stripe match those present in the auth token payload. - * If they do not match, the UserSubscription item is updated in the db, and the User's - * auth token payload values are updated as well. - */ -export const checkSubscriptionStatus = mwAsyncCatchWrapper(async (req, res, next) => { - if (!res.locals?.authenticatedUser) return next(new AuthError("User not found")); - - const { subscription, id: userID } = res.locals.authenticatedUser; - - if (subscription) { - const { - id: subID, - status: status_inDB, - currentPeriodEnd: currentPeriodEnd_inDB, - } = subscription; - - // Fetch fresh data from Stripe - const stripeRetrieveSubResult = await stripe.subscriptions.retrieve(subID); - - // Normalize the object returned from Stripe - const upToDateSubInfo = UserSubscription.normalizeStripeFields(stripeRetrieveSubResult); - - // If DB values are stale, update the user's sub in the db - if ( - upToDateSubInfo.status !== status_inDB || - upToDateSubInfo.currentPeriodEnd.getTime() !== new Date(currentPeriodEnd_inDB).getTime() - ) { - /* Do NOT use Stripe's `createdAt` value for the UserSubscription SK, because it may - not match the value in the DB. Instead, use the value from res.locals.userSubscription - (should always be present here). */ - if (!res.locals?.userSubscription?.createdAt) - return next(new InternalServerError("Invalid subscription details")); - - const updatedSub = await UserSubscription.updateItem( - { - userID, - sk: UserSubscription.getFormattedSK(userID, res.locals.userSubscription.createdAt), - }, - { - update: { - status: upToDateSubInfo.status, - currentPeriodEnd: upToDateSubInfo.currentPeriodEnd, - }, - } - ); - - res.locals.authenticatedUser.subscription = { - ...res.locals.authenticatedUser.subscription, - ...updatedSub, - }; - } - } - - next(); -}); diff --git a/src/middleware/stripeSubscriptions/createCustomerPortalLink.ts b/src/middleware/stripeSubscriptions/createCustomerPortalLink.ts deleted file mode 100644 index e9a1f94d..00000000 --- a/src/middleware/stripeSubscriptions/createCustomerPortalLink.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { stripe } from "@/lib/stripe/stripeClient.js"; -import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; -import type { RestApiRequestBodyByPath } from "@/types/open-api.js"; -import { AuthError } from "@/utils/httpErrors.js"; - -/** - * This middleware creates a Stripe Customer Portal link, which allows the User to - * manage their subscription and payment methods. - */ -export const createCustomerPortalLink = mwAsyncCatchWrapper< - RestApiRequestBodyByPath["/subscriptions/customer-portal"] ->(async (req, res, next) => { - if (!res.locals?.authenticatedUser) return next(new AuthError("User not found")); - - const stripeLink = await stripe.billingPortal.sessions.create({ - customer: res.locals.authenticatedUser.stripeCustomerID, - return_url: req.body.returnURL, - }); - - res.json({ stripeLink: stripeLink.url }); -}); diff --git a/src/middleware/stripeSubscriptions/findOrCreateStripeSubscription.ts b/src/middleware/stripeSubscriptions/findOrCreateStripeSubscription.ts deleted file mode 100644 index 17109996..00000000 --- a/src/middleware/stripeSubscriptions/findOrCreateStripeSubscription.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { isString, getTypeSafeError } from "@nerdware/ts-type-safety-utils"; -import { stripe } from "@/lib/stripe/stripeClient.js"; -import { mwAsyncCatchWrapper } from "@/middleware/helpers.js"; -import { UserSubscription } from "@/models/UserSubscription/UserSubscription.js"; -import { PaymentRequiredError, AuthError } from "@/utils/httpErrors.js"; -import { logger } from "@/utils/logger.js"; -import type { - StripeSubscriptionWithClientSecret, - StripeCustomerWithClientSecret, -} from "@/lib/stripe/types.js"; -import type { RestApiRequestBodyByPath } from "@/types/open-api.js"; -import type Stripe from "stripe"; - -/** - * This middleware serves as the primary endpoint for the checkout/payment process. - * If the supplied payment details result in a successful payment, or if additional - * user input is required to confirm the payment (e.g., for [3D-Secure/SCA][3ds-info]), - * the User is provided with a `checkoutCompletionInfo` object (see the OpenAPI schema), - * and their auth token payload is updated with the new subscription details. If a - * non-zero amount is owed (TRIAL/PROMO_CODE) and the payment-intent fails, the request - * results in a 402 Payment Required error. - * - * After checking to make sure the User is authenticated, the Stripe API is used to - * attach the provided payment method to the customer and set it as their default - * payment method. The User may already have one ore more subscriptions (e.g., if - * they previously created a sub but didn't complete the payment process for it), - * in which case the array of subscriptions returned from Stripe is searched for a - * subscription that _**is not expired**_. - * - * If a non-expired subscription _**is not**_ found, one is upserted into the db. - * Note that `upsert` is used because a sub may already exist in the db, but it may - * be _**expired**_, in which case it's desirable to simply overwrite it so the db - * isn't populated with dangling sub items that will never be used. - * - * Once a valid, non-expired subscription has been obtained (either from Stripe or - * the `upsert` operation), the `status` of the `payment_intent` on the sub's latest - * invoice is checked — any value other than "succeeded" results in a 402 Payment - * Required error. If the status is "succeeded", the User's auth token payload is - * updated with the new subscription details - they're now able to use Fixit SaaS! - * - * [3ds-info]: https://stripe.com/docs/payments/3d-secure - */ -export const findOrCreateStripeSubscription = mwAsyncCatchWrapper< - RestApiRequestBodyByPath["/subscriptions/submit-payment"] ->(async (req, res, next) => { - if (!res.locals?.authenticatedUser) return next(new AuthError("User not found")); - - try { - const { paymentMethodID, selectedSubscription, promoCode } = req.body; - const { authenticatedUser } = res.locals; - - // This will be set if the User already has a valid subscription - let nonExpiredSubscription: StripeSubscriptionWithClientSecret | null | undefined; - - // Attach the payment method to the customer - await stripe.paymentMethods.attach(paymentMethodID, { - customer: authenticatedUser.stripeCustomerID, - }); - - // Change the default invoice settings on the customer to the new payment method. - const { subscriptions } = (await stripe.customers.update(authenticatedUser.stripeCustomerID, { - invoice_settings: { default_payment_method: paymentMethodID }, - expand: ["subscriptions.data.latest_invoice.payment_intent"], - })) as Stripe.Response; - - // See if there's an existing non-expired subscription - if (!!subscriptions && (subscriptions?.data?.length ?? 0) >= 1) { - nonExpiredSubscription = subscriptions.data.find( - (sub) => sub?.id.startsWith("sub") && sub?.status !== "incomplete_expired" - ); - } - - // If the User already has a valid sub, obtain the values we need from that one, else upsert one. - const { - id: subID, - currentPeriodEnd, - latest_invoice, - status: currentSubStatus, - } = nonExpiredSubscription - ? UserSubscription.normalizeStripeFields( - nonExpiredSubscription - ) - : await UserSubscription.upsertOne({ - user: authenticatedUser, - selectedSubscription, - promoCode, - }); - - /* HANDLE TRIAL/PROMO_CODE - The `latest_invoice` will not have a `payment_intent.id` in checkout situations - which don't involve an immediate payment — i.e., if the user selected a TRIAL, - or provided a VIP `promoCode` which grants them 100% off at checkout. */ - if (!latest_invoice?.payment_intent?.id) { - /* Just to be sure the sub/payment are in the expected state, assertions are - made regarding the expected TRIAL/PROMO_CODE. If neither conditions apply, - Stripe should have provided `payment_intent.id`, so an error is thrown. */ - const isTrialSub = selectedSubscription === "TRIAL" && currentSubStatus === "trialing"; - const wasVipPromoCodeApplied = - !!promoCode && latest_invoice?.discount?.coupon?.percent_off === 100; - - if (!isTrialSub && !wasVipPromoCodeApplied) { - throw new Error("Stripe Error: Failed to retrieve payment details"); - } - - // For TRIAL/PROMO_CODE subs, `latest_invoice.paid` should be `true` here - res.locals.checkoutCompletionInfo = { - isCheckoutComplete: latest_invoice?.paid === true, - }; - } else { - // Confirm intent with collected payment method - const { - status: paymentStatus, - client_secret: clientSecret, - invoice, - } = await stripe.paymentIntents.confirm(latest_invoice.payment_intent.id, { - payment_method: paymentMethodID, - mandate_data: { - customer_acceptance: { - type: "online", - online: { - ip_address: req.ip!, - user_agent: req.get("user-agent")!, - }, - }, - }, - expand: ["invoice"], // expand to get `invoice.paid` - }); - - // Sanity-check: ensure the paymentStatus and clientSecret are strings - if (!isString(paymentStatus) || !isString(clientSecret)) { - throw new Error("Stripe Error: payment confirmation failed."); - } - - const isCheckoutComplete = - ["succeeded", "requires_action"].includes(paymentStatus) && - (invoice as Stripe.Invoice)?.paid === true; // invoice is expanded - - if (!isCheckoutComplete) throw new Error("Your payment was declined."); - - // Update `res.locals.checkoutCompletionInfo` - res.locals.checkoutCompletionInfo = { - isCheckoutComplete, - ...(clientSecret && { clientSecret }), - }; - } - - // Check the sub's status - const { status: subStatusAfterPayment } = await stripe.subscriptions.retrieve(subID); - - // Update `res.locals.authenticatedUser` with the new subscription details: - res.locals.authenticatedUser.subscription = { - id: subID, - currentPeriodEnd, - status: subStatusAfterPayment, - }; - - // If an error occurs, ensure the 402 status code is provided. - } catch (err: unknown) { - const error = getTypeSafeError(err); - logger.stripe(error, "findOrCreateStripeSubscription"); - throw new PaymentRequiredError(error.message); - } - - next(); -}); diff --git a/src/middleware/stripeSubscriptions/index.ts b/src/middleware/stripeSubscriptions/index.ts deleted file mode 100644 index a03c6ce0..00000000 --- a/src/middleware/stripeSubscriptions/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./checkPromoCode.js"; -export * from "./checkSubscriptionStatus.js"; -export * from "./createCustomerPortalLink.js"; -export * from "./findOrCreateStripeSubscription.js"; diff --git a/src/middleware/stripeWebhooks/_handleStripeWebhookEvent.ts b/src/middleware/stripeWebhooks/_handleStripeWebhookEvent.ts deleted file mode 100644 index e075ba8b..00000000 --- a/src/middleware/stripeWebhooks/_handleStripeWebhookEvent.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { getTypeSafeError, safeJsonStringify } from "@nerdware/ts-type-safety-utils"; -import { stripe } from "@/lib/stripe/stripeClient.js"; -import { ENV } from "@/server/env"; -import { logger } from "@/utils/logger.js"; -import { connectAccountUpdated } from "./connectAccountUpdated.js"; -import { customerSubscriptionDeleted } from "./customerSubscriptionDeleted.js"; -import { customerSubscriptionUpdated } from "./customerSubscriptionUpdated.js"; -import type { Request, Response, NextFunction } from "express"; -import type Stripe from "stripe"; - -/** - * This middleware constructs, validates, and logs all Stripe webhook events. If - * the event is valid, it then looks for a handler for the event's `type` (e.g., - * `"customer.subscription.updated"`), and invokes the handler if one is found. - * - * - This API places each event-type into 1 of 3 categories: - * - * 1. _`actionable`_ events were registered with Stripe and have associated - * event-handlers. - * - * 2. _`non-actionable`_ events were registered with Stripe but do NOT have - * associated event-handlers, typically because they contain data that is - * not persisted by the Fixit back-end at this time. - * - * 3. _`unhandled`_ events have NOT been registered with Stripe and therefore - * are NOT expected to be received. If an unhandled event is received, it - * is logged as an error. - */ -export const handleStripeWebhookEvent = async (req: Request, res: Response, next: NextFunction) => { - let event: Stripe.Event | undefined; - - try { - // Construct Stripe event object - event = stripe.webhooks.constructEvent( - req.body, - req.headers["stripe-signature"] as string | string[] | Buffer, - ENV.STRIPE.WEBHOOKS_SECRET - ); - } catch (err: unknown) { - const error = getTypeSafeError(err); - logger.stripe(error, "Webhook signature verification failed"); - res.status(400).send(`Webhook Error: ${error.message}`); - } - - if (!event) return next("ERROR: Stripe webook event object not found"); - - const eventHandler = STRIPE_WEBHOOK_EVENT_HANDLERS?.[event.type]; - - /* - If eventHandler is - truthy --> actionable event - null --> non-actionable event - undefined --> unhandled event - */ - - // Log the webhook and its actionability - logger.webhook( - safeJsonStringify( - { - isActionableEvent: !!eventHandler, - isHandledEvent: eventHandler !== undefined, - eventType: event.type, - eventObject: event.data.object, - }, - null, - 2 - ) - ); - - // If an event handler exists, invoke it and acknowledge receipt of the event - if (eventHandler) { - await eventHandler(event.data.object); - res.json({ received: true }); - } - - next(); -}; - -/** - * A hash table mapping Stripe webhook events to event-handlers. - * - * - Each event handler takes the event's `data.object` as its sole argument. - * - Each event handler is responsible for logging any errors it encounters. - * - Events with function-handlers are considered _actionable_ events. - * - Events with `null`-handlers are considered _non-actionable_ events. - * - Events not included are considered _unhandled_ events. - * - * #### Customer Portal Events - * - * The events listed below may be triggered by actions taken by Customers in the Stripe-provided - * customer portal. Descriptions of each event and the data contained in its `data.object` can be - * found [here][stripe-portal-events]. - * - * - `billing_portal.configuration.created` - * - `billing_portal.configuration.updated` - * - `billing_portal.session.created` - * - `customer.subscription.deleted` - * - `customer.subscription.paused` - * - `customer.subscription.resumed` - * - `customer.subscription.updated` - * - `customer.tax_id.created` - * - `customer.tax_id.deleted` - * - `customer.tax_id.updated` - * - `customer.updated` - * - `payment_method.attached` - * - `payment_method.detached` - * - * [stripe-portal-events]: https://stripe.com/docs/customer-management/integrate-customer-portal#webhooks - */ -const STRIPE_WEBHOOK_EVENT_HANDLERS: Record Promise) | null> = { - "account.application.authorized": null, - "account.application.deauthorized": null, - "account.external_account.created": null, - "account.external_account.deleted": null, - "account.external_account.updated": null, - "account.updated": connectAccountUpdated, - "billing_portal.configuration.created": null, - "billing_portal.configuration.updated": null, - "billing_portal.session.created": null, - "customer.subscription.created": customerSubscriptionUpdated, - "customer.subscription.deleted": customerSubscriptionDeleted, - "customer.subscription.paused": customerSubscriptionUpdated, - "customer.subscription.pending_update_applied": customerSubscriptionUpdated, - "customer.subscription.pending_update_expired": customerSubscriptionUpdated, - "customer.subscription.resumed": customerSubscriptionUpdated, - "customer.subscription.trial_will_end": customerSubscriptionUpdated, - "customer.subscription.updated": customerSubscriptionUpdated, - "customer.created": null, - "customer.deleted": null, - "customer.updated": null, - "customer.discount.created": null, - "customer.discount.deleted": null, - "customer.discount.updated": null, - "customer.source.created": null, - "customer.source.deleted": null, - "customer.source.expiring": null, - "customer.source.updated": null, - "customer.tax_id.created": null, - "customer.tax_id.deleted": null, - "customer.tax_id.updated": null, - "payment_method.attached": null, - "payment_method.detached": null, -}; diff --git a/src/middleware/stripeWebhooks/index.ts b/src/middleware/stripeWebhooks/index.ts deleted file mode 100644 index 9229705f..00000000 --- a/src/middleware/stripeWebhooks/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// The root event-handler middleware: -export * from "./_handleStripeWebhookEvent.js"; -// Handlers for actionable events: -export * from "./connectAccountUpdated.js"; -export * from "./customerSubscriptionDeleted.js"; -export * from "./customerSubscriptionUpdated.js"; diff --git a/src/middleware/util-mw/index.ts b/src/middleware/util-mw/index.ts deleted file mode 100644 index b29eac50..00000000 --- a/src/middleware/util-mw/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./logReqReceived.js"; -export * from "./sendRESTJsonResponse.js"; diff --git a/src/middleware/util-mw/sendRESTJsonResponse.ts b/src/middleware/util-mw/sendRESTJsonResponse.ts deleted file mode 100644 index 6babe376..00000000 --- a/src/middleware/util-mw/sendRESTJsonResponse.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { RestApiRequestHandler } from "@/middleware/helpers.js"; - -/** - * This middleware is a fallback response handler which reads the `res.locals` object and returns - * an appropriate response based on the provided fields as described in the table below. - * - * #### Why take this approach? - * - * It is often the case that a given middleware function may generate data that needs to be - * returned to the client, _but_ that middlware may be used in multiple routes/endpoints, and - * it isn't aware of what impact (if any) other middleware in the chain may have on the response. - * Perhaps other functions also need to add data to the response, or perhaps they need to read - * the data it generates. For example, the `auth/login` and `auth/token` endpoints both respond - * with a `token` BUT may also include pre-fetched `userItems`. - * - * To address this issue and allow greater flexibility in regard to middleware design, middleware - * functions which generate data for the response and/or other middleware add their data to the - * `res.locals` object for down-stream consumption. - * - * Conversely, some middleware functions will call `res.json` directly if they're designed to work - * within a particular request-response cycle that's limited in scope. For example, the purpose of - * the `subscriptions/customer-portal` endpoint is solely to provide a Stripe Customer Portal link, - * so the implementing middleware can send the `stripeLink` response without needing to worry about - * other middleware impacting the response. - * - * | `res.locals` Field | Resultant Response Field | - * | :----------------------- | :---------------------------------------------------------- | - * | `authToken` | `token: res.locals.authToken` | - * | `promoCodeInfo` | `promoCodeInfo: res.locals.promoCodeInfo` | - * | `checkoutCompletionInfo` | `checkoutCompletionInfo: res.locals.checkoutCompletionInfo` | - * | `stripeLink` | `stripeLink: res.locals.stripeLink` | - * | `userItems` | `userItems: res.locals.userItems` | - */ -export const sendRESTJsonResponse: RestApiRequestHandler = (req, res) => { - // Get available `res.locals` fields which are associated with a response field: - const { authToken, promoCodeInfo, checkoutCompletionInfo, stripeLink, userItems } = res.locals; - - // "token" | "promoCodeInfo" | "checkoutPaymentInfo" | "stripeLink" | "userItems" - - res.json({ - ...(!!authToken && { token: authToken }), - ...(!!promoCodeInfo && { promoCodeInfo }), - ...(!!checkoutCompletionInfo && { checkoutCompletionInfo }), - ...(!!stripeLink && { stripeLink }), - ...(!!userItems && { userItems }), - }); -}; diff --git a/src/models/Contact/Contact.ts b/src/models/Contact/Contact.ts index d7d8641c..bf76229d 100644 --- a/src/models/Contact/Contact.ts +++ b/src/models/Contact/Contact.ts @@ -3,8 +3,7 @@ import { isValidHandle } from "@nerdware/ts-string-helpers"; import { userModelHelpers } from "@/models/User/helpers.js"; import { COMMON_ATTRIBUTES } from "@/models/_common/modelAttributes.js"; import { ddbTable } from "@/models/ddbTable.js"; -import { contactModelHelpers } from "./helpers.js"; -import { CONTACT_SK_PREFIX_STR } from "./regex.js"; +import { contactModelHelpers, CONTACT_SK_PREFIX_STR } from "./helpers.js"; import type { ItemTypeFromSchema, ItemCreationParameters } from "@nerdware/ddb-single-table"; /** @@ -22,7 +21,7 @@ class ContactModel extends Model { type: "string", alias: "id", // Contact "sk" contains the "contactUserID" default: (contact: { data?: string }) => - contact?.data ? contactModelHelpers.id.format(contact.data) : undefined, + contact.data ? contactModelHelpers.id.format(contact.data) : undefined, validate: contactModelHelpers.id.isValid, required: true, }, @@ -50,18 +49,16 @@ class ContactModel extends Model { readonly isValidID = contactModelHelpers.id.isValid; } +/** Contact Model */ export const Contact = new ContactModel(); /** The shape of a `Contact` object returned from ContactModel methods. */ export type ContactItem = ItemTypeFromSchema; /** `Contact` item params for `createItem()`. */ -export type ContactItemCreationParams = ItemCreationParameters; +export type ContactCreateItemParams = ItemCreationParameters; -/** - * The shape of a `Contact` object in the DB. - * > This type is used to mock `@aws-sdk/lib-dynamodb` responses. - */ +/** The shape of a raw/unaliased `Contact` object in the DB. */ export type UnaliasedContactItem = ItemTypeFromSchema< typeof ContactModel.schema, { diff --git a/src/models/Contact/helpers.ts b/src/models/Contact/helpers.ts index d586b167..f7927e52 100644 --- a/src/models/Contact/helpers.ts +++ b/src/models/Contact/helpers.ts @@ -1,10 +1,15 @@ -import { createModelHelpers } from "@/models/_common/modelHelpers.js"; -import { CONTACT_SK_PREFIX_STR as SK_PREFIX, CONTACT_SK_REGEX } from "./regex.js"; +import { userModelHelpers } from "@/models/User/helpers.js"; +import { createMapOfStringAttrHelpers, getCompoundAttrRegex, DELIMETER } from "@/models/_common"; -export const contactModelHelpers = createModelHelpers({ +export const CONTACT_SK_PREFIX_STR = "CONTACT"; + +export const contactModelHelpers = createMapOfStringAttrHelpers({ id: { - regex: CONTACT_SK_REGEX, - /** Returns a formatted Contact "id" value (alias for "sk" attribute) */ - format: (contactUserID: string) => `${SK_PREFIX}#${contactUserID}`, + /** Validation regex for Contact IDs. */ + regex: getCompoundAttrRegex([CONTACT_SK_PREFIX_STR, userModelHelpers.id.regex]), + /** Sanitizes a Contact ID value. */ + sanitize: userModelHelpers.id.sanitize, + /** Returns a formatted Contact "id" value. */ + format: (contactUserID: string) => `${CONTACT_SK_PREFIX_STR}${DELIMETER}${contactUserID}`, }, }); diff --git a/src/models/Contact/index.ts b/src/models/Contact/index.ts index 6d78ae8e..6afa1ce2 100644 --- a/src/models/Contact/index.ts +++ b/src/models/Contact/index.ts @@ -1 +1,2 @@ export * from "./Contact.js"; +export * from "./helpers.js"; diff --git a/src/models/Contact/regex.ts b/src/models/Contact/regex.ts deleted file mode 100644 index 0185c669..00000000 --- a/src/models/Contact/regex.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { USER_ID_REGEX_STR } from "@/models/User/regex.js"; - -export const CONTACT_SK_PREFIX_STR = "CONTACT"; -export const CONTACT_SK_REGEX_STR = `^${CONTACT_SK_PREFIX_STR}#${USER_ID_REGEX_STR}$`; -export const CONTACT_SK_REGEX = new RegExp(CONTACT_SK_REGEX_STR); diff --git a/src/models/Invoice/Invoice.ts b/src/models/Invoice/Invoice.ts index f3137eaa..bb4b257a 100644 --- a/src/models/Invoice/Invoice.ts +++ b/src/models/Invoice/Invoice.ts @@ -1,17 +1,15 @@ import { Model } from "@nerdware/ddb-single-table"; import { isString } from "@nerdware/ts-type-safety-utils"; -import { isValidStripeID } from "@/lib/stripe/isValidStripeID.js"; +import { isValidStripeID } from "@/lib/stripe/helpers.js"; import { userModelHelpers } from "@/models/User/helpers.js"; import { workOrderModelHelpers as woModelHelpers } from "@/models/WorkOrder/helpers.js"; import { COMMON_ATTRIBUTES } from "@/models/_common/modelAttributes.js"; import { ddbTable } from "@/models/ddbTable.js"; import { INVOICE_ENUM_CONSTANTS } from "./enumConstants.js"; -import { invoiceModelHelpers } from "./helpers.js"; -import { INVOICE_SK_PREFIX_STR } from "./regex.js"; +import { invoiceModelHelpers, INVOICE_SK_PREFIX_STR } from "./helpers.js"; import type { ItemTypeFromSchema, ItemCreationParameters, - ItemParameters, ModelSchemaOptions, } from "@nerdware/ddb-single-table"; @@ -29,10 +27,8 @@ class InvoiceModel extends Model { sk: { type: "string", alias: "id", - default: (invoice: { pk: string; createdAt: Date }) => - invoice?.pk && invoice?.createdAt - ? invoiceModelHelpers.id.format(invoice.pk, invoice.createdAt) - : undefined, + default: ({ pk: createdByUserID }: { pk: string }) => + createdByUserID ? invoiceModelHelpers.id.format(createdByUserID) : undefined, validate: invoiceModelHelpers.id.isValid, required: true, }, @@ -48,10 +44,9 @@ class InvoiceModel extends Model { }, amount: { type: "number", - /* Invoice amount is a non-zero integer reflecting USD centage, - where 100 = 100 ¢ = $1 USD. For i18n purposes, currency conversions - will be handled through the Stripe API. */ - validate: (value: number) => Number.isSafeInteger(value) && value > 0, + /* Invoice amount is a non-zero integer reflecting USD centage, where 100 = 100 ¢ = $1 USD. + For i18n purposes, currency conversions will be handled through the Stripe API. */ + validate: invoiceModelHelpers.amount.isValid, required: true, }, status: { @@ -82,26 +77,21 @@ class InvoiceModel extends Model { }); } - // INVOICE MODEL — Instance properties and methods: - readonly STATUSES = INVOICE_ENUM_CONSTANTS.STATUSES; + // INVOICE MODEL — Instance properties: readonly SK_PREFIX = INVOICE_SK_PREFIX_STR; + readonly STATUSES = INVOICE_ENUM_CONSTANTS.STATUSES; } +/** Invoice Model */ export const Invoice = new InvoiceModel(); /** The shape of an `Invoice` object returned from InvoiceModel methods. */ export type InvoiceItem = ItemTypeFromSchema; /** `Invoice` item params for `createItem()`. */ -export type InvoiceItemCreationParams = ItemCreationParameters; - -/** `Invoice` item params for `updateItem()`. */ -export type InvoiceItemUpdateParams = ItemParameters; +export type InvoiceCreateItemParams = ItemCreationParameters; -/** - * The shape of an `Invoice` object in the DB. - * > This type is used to mock `@aws-sdk/lib-dynamodb` responses. - */ +/** The shape of a raw/unaliased `Invoice` object in the DB. */ export type UnaliasedInvoiceItem = ItemTypeFromSchema< typeof InvoiceModel.schema, { diff --git a/src/models/Invoice/helpers.ts b/src/models/Invoice/helpers.ts index c9a98a8c..bb24e92a 100644 --- a/src/models/Invoice/helpers.ts +++ b/src/models/Invoice/helpers.ts @@ -1,24 +1,22 @@ -import { isDate } from "@nerdware/ts-type-safety-utils"; -import { createModelHelpers } from "@/models/_common/modelHelpers.js"; -import { getUnixTimestampUUID } from "@/utils/uuid.js"; -import { INVOICE_SK_PREFIX_STR as SK_PREFIX, INVOICE_SK_REGEX } from "./regex.js"; +import { isSafeInteger } from "@nerdware/ts-type-safety-utils"; +import { userModelHelpers } from "@/models/User/helpers.js"; +import { createHelpersForStrAttr, getCompoundAttrRegex, DELIMETER } from "@/models/_common"; +import { getRandomUUIDv4, UUID_REGEX } from "@/utils/uuid.js"; -export const invoiceModelHelpers = createModelHelpers({ - id: { - regex: INVOICE_SK_REGEX, +export const INVOICE_SK_PREFIX_STR = "INV"; - /** - * Invoice "id" value formatter. - * - * @param {Date|string} createdAt - The Invoice's "createdAt" timestamp value represented as - * either a Date object or unix timestamp UUID string. If provided as a Date object, it will - * be converted to a Unix timestamp UUID string. - * - * @returns {string} A formatted Invoice "id" value (alias for "sk" attribute). - */ - format: (createdByUserID: string, createdAt: Date | string) => { - // prettier-ignore - return `${SK_PREFIX}#${createdByUserID}#${isDate(createdAt) ? getUnixTimestampUUID(createdAt) : createdAt}`; +export const invoiceModelHelpers = { + id: createHelpersForStrAttr("id", { + /** Invoice ID validation regex. */ + regex: getCompoundAttrRegex([INVOICE_SK_PREFIX_STR, userModelHelpers.id.regex, UUID_REGEX]), + /** Sanitizes an Invoice ID value. */ + sanitize: (str: string) => str.replace(/[^a-zA-Z0-9_@#-]/g, ""), // handle chars, UUID chars, and the delimeter + /** Invoice "id" value formatter. */ + format: (createdByUserID: string) => { + return `${INVOICE_SK_PREFIX_STR}${DELIMETER}${createdByUserID}${DELIMETER}${getRandomUUIDv4()}`; }, + }), + amount: { + isValid: (value?: unknown) => isSafeInteger(value) && value > 0, }, -}); +}; diff --git a/src/models/Invoice/index.ts b/src/models/Invoice/index.ts index 917e1406..9649e73f 100644 --- a/src/models/Invoice/index.ts +++ b/src/models/Invoice/index.ts @@ -1 +1,2 @@ export * from "./Invoice.js"; +export * from "./helpers.js"; diff --git a/src/models/Invoice/regex.ts b/src/models/Invoice/regex.ts deleted file mode 100644 index eead6973..00000000 --- a/src/models/Invoice/regex.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { USER_ID_REGEX_STR } from "@/models/User/regex.js"; -import { UUID_V1_REGEX_STR } from "@/utils/regex.js"; - -export const INVOICE_SK_PREFIX_STR = "INV"; -export const INVOICE_SK_REGEX_STR = `^${INVOICE_SK_PREFIX_STR}#${USER_ID_REGEX_STR}#${UUID_V1_REGEX_STR}$`; -export const INVOICE_SK_REGEX = new RegExp(INVOICE_SK_REGEX_STR); diff --git a/src/models/Location/Location.test.ts b/src/models/Location/Location.test.ts index 198e3346..99c181f6 100644 --- a/src/models/Location/Location.test.ts +++ b/src/models/Location/Location.test.ts @@ -10,7 +10,7 @@ const TEST_LOCATION = { }; /** {@link TEST_LOCATION} in compound-string form. */ -const TEST_LOCATION_COMPOUND_STRING = "USA#California#San_Francisco#123_Main_St.#Apt_4"; +const TEST_LOCATION_COMPOUND_STRING = "USA#California#San%20Francisco#123%20Main%20St.#Apt%204"; describe("Location Model", () => { /* Explicitly set TEST_LOCATION prototype so `toStrictEqual` works @@ -60,7 +60,7 @@ describe("Location Model", () => { const result1 = Location.convertToCompoundString(location1); const result2 = Location.convertToCompoundString(location2); expect(result1).toStrictEqual(TEST_LOCATION_COMPOUND_STRING); - expect(result2).toBe("USA#California#San_Francisco#456_Foo_Blvd."); + expect(result2).toBe("USA#California#San%20Francisco#456%20Foo%20Blvd.#"); }); }); diff --git a/src/models/Location/Location.ts b/src/models/Location/Location.ts index 9899e569..f334d0f7 100644 --- a/src/models/Location/Location.ts +++ b/src/models/Location/Location.ts @@ -1,5 +1,6 @@ import { isString } from "@nerdware/ts-type-safety-utils"; -import { LOCATION_COMPOSITE_REGEX } from "./regex.js"; +import { getCompoundAttrString, parseCompoundAttrString } from "@/models/_common"; +import { LOCATION_COMPOUND_STR_REGEX } from "./helpers.js"; import type { Location as GqlSchemaLocationType } from "@/types/graphql.js"; /** @@ -24,10 +25,6 @@ export class Location implements GqlSchemaLocationType { public static DEFAULT_COUNTRY = "USA" as const satisfies string; - public static get KEYS(): Array { - return ["country", "region", "city", "streetLine1", "streetLine2"]; - } - /** * Convert a Location object into a DDB compound attribute string. * This is used in `transformValue.toDB` methods of DdbSingleTable model schema. @@ -39,45 +36,10 @@ export class Location implements GqlSchemaLocationType { streetLine1: streetLine1RawInput, streetLine2: streetLine2RawInput = null, }: Location): string => { - // this returns the "location" composite value as a single string with "#" as the field delimeter - return [ - countryRawInput, - regionRawInput, - cityRawInput, - streetLine1RawInput, - streetLine2RawInput, - ].reduce((accum: string, currentRawInput, index) => { - // "streetLine2RawInput" is optional - skip processing if null - if (!isString(currentRawInput)) return accum; - - /* For all "location" values, underscores are not valid in the raw input, but we can't - catch underscores in the Model attribute validation regex since spaces are replaced with - underscores. We could `throw new Error` from this `transformValue` fn if the raw input - includes an underscore, but such input-validation logic falls outside of the scope and - intended purpose of the `transformValue` methods. In the interest of keeping the input- - validation logic within the `validate`/`validateItem` methods, this fn simply replaces - underscores with the string literal "%_UNDERSCORE_%"; since "%" signs are invalid chars, - the validate field regex catches the invalid input, and the resultant error msg informs - the user that underscores are invalid. */ - let formattedInput = currentRawInput.replace(/_/g, "%_UNDERSCORE_%"); - - // For all "location" values, replace spaces with underscores - formattedInput = formattedInput.replace(/\s/g, "_"); - - /* "streetLine2RawInput" (index 4) may include "#" chars (e.g., "Ste_#_398"), so - any provided number signs need to be replaced since they're used as the composite - value delimeter. Here they're replaced with the string literal "NUMSIGN". - For all other "location" fields, num signs are invalid, so like the treatment of - underscores described above, invalid num signs are replaced with the string literal - "%_NUMSIGN_%" so the "validation" function can catch it. */ - formattedInput = formattedInput.replace(/#/g, index === 4 ? "NUMSIGN" : "%_NUMSIGN_%"); - - /* All segments of the "location" composite attribute value except for the - first one ("country") must be prefixed with "#", the delimeter. */ - accum = index !== 0 ? `${accum}#${formattedInput}` : formattedInput; - - return accum; - }, ""); // <-- reducer init accum is an empty string + return getCompoundAttrString( + [countryRawInput, regionRawInput, cityRawInput, streetLine1RawInput, streetLine2RawInput], + { shouldUrlEncode: true } + ); }; /** @@ -85,36 +47,23 @@ export class Location implements GqlSchemaLocationType { * This is used in `transformValue.fromDB` methods of DdbSingleTable model schema. */ public static parseCompoundString = (locationCompoundStr: string) => { + // Sanity check: If the input is not a string, just return it as-is if (!isString(locationCompoundStr)) return locationCompoundStr; - // Split the composite value string using the "#" delimeter - const locationComponents = locationCompoundStr.split("#"); - // If length is less than 4, throw an error - if (locationComponents.length < 4) { + // Split the compound value string using the "#" delimeter + const locationComponents = parseCompoundAttrString(locationCompoundStr, { + shouldUrlDecode: true, + }); + // If length is not 5, throw an error + if (locationComponents.length !== 5) throw new Error( `Invalid Location: "${locationCompoundStr}" is not a valid Location compound string.` ); - } - - // Reduce the array into a Location object - const locationObject = locationComponents.reduce((accum, dbValue, index) => { - let formattedOutput = dbValue; - - // Format non-null values - if (isString(formattedOutput)) { - // Replace "NUMSIGN" string literal with "#" (for streetLine2) - formattedOutput = formattedOutput.replace(/NUMSIGN/g, "#"); - // Replace underscores with spaces - formattedOutput = formattedOutput.replace(/_/g, " "); - } - - // Get location key from array, and set the Location K-V - accum[Location.KEYS[index]!] = formattedOutput; - return accum; - }, {} as Location); + // Destructure the array's Location fields + const [country, region, city, streetLine1, streetLine2] = locationComponents; // Provide the `locationObject` to the Location constructor - return Location.fromParams(locationObject); + return Location.fromParams({ country, region, city, streetLine1, streetLine2 }); }; /** @@ -122,7 +71,7 @@ export class Location implements GqlSchemaLocationType { */ public static validateCompoundString = (locationCompoundStr?: unknown) => { // The test method doesn't throw when given invalid arg types, so it's safe to cast here. - return LOCATION_COMPOSITE_REGEX.test(locationCompoundStr as string); + return LOCATION_COMPOUND_STR_REGEX.test(locationCompoundStr as string); }; /** diff --git a/src/models/Location/helpers.ts b/src/models/Location/helpers.ts new file mode 100644 index 00000000..cc8f76e8 --- /dev/null +++ b/src/models/Location/helpers.ts @@ -0,0 +1,61 @@ +import { getCompoundAttrRegex } from "@/models/_common"; + +/** Sanitizes a user-provided street-address string by removing any non-permitted chars. */ +export const sanitizeStreetAddress = (value: string): string => { + return value.replace(/[^\p{Script=Latin}\s\d'.:,#-]/giu, "").trim(); +}; + +/** Returns a boolean indicating whether a user-provided street-address string is valid. */ +export const isValidStreetAddress = (value: string): boolean => { + return /^[\p{Script=Latin}\s\d'.:,#-]{2,}$/iu.test(value); +}; + +/** + * Regex pattern for validating place-name strings which have been URL-encoded to get rid of spaces. + * + * This pattern requires a minimum of two characters, and permits the following characters: + * + * - Unicode Latin-script characters ([see table][wikipedia-latin-chars]) + * - `\p{Script=Latin}` is used as a more i18n-friendly alternative to `[a-zA-Z]` + * - Apostrophes (`'`) + * - Periods (`.`) + * - Hyphens (`-`) + * - Percent signs (`%`) + * - Numbers (`0-9`) + * - Underscores (`_`) + * + * [wikipedia-latin-chars]: https://en.wikipedia.org/w/index.php?title=Latin_script_in_Unicode&oldid=1210023145#Table_of_characters + */ +const URL_ENCODED_PLACE_NAME_REGEX = /[\p{Script=Latin}%0-9'._-]{2,}/iu; + +/** + * Regex pattern string for validating street-address strings (line 1 or line 2) which have been + * URL-encoded to get rid of spaces. + * + * This pattern requires a minimum of two characters, and permits all characters permitted in the + * {@link URL_ENCODED_PLACE_NAME_REGEX} pattern, as well as the following characters: + * + * - Colons (`:`) + * - Commas (`,`) + * - Number signs (`#`) + */ +const URL_ENCODED_STREET_ADDRESS_REGEX = /[\p{Script=Latin}%0-9'._:,#-]{2,}/iu; + +/** + * Location Composite Value Pattern: + * + * `[COUNTRY]#[STATE]#[CITY]#[STREET_LINE_1]#[STREET_LINE_2]` + */ +export const LOCATION_COMPOUND_STR_REGEX = getCompoundAttrRegex( + [ + URL_ENCODED_PLACE_NAME_REGEX, // country + URL_ENCODED_PLACE_NAME_REGEX, // state/region + URL_ENCODED_PLACE_NAME_REGEX, // city + URL_ENCODED_STREET_ADDRESS_REGEX, // street line 1 + { + regex: URL_ENCODED_STREET_ADDRESS_REGEX, // street line 2 + required: false, + }, + ], + { regexFlags: "iu" } +); diff --git a/src/models/Location/index.ts b/src/models/Location/index.ts index d9df2199..c476bb7f 100644 --- a/src/models/Location/index.ts +++ b/src/models/Location/index.ts @@ -1 +1,2 @@ export * from "./Location.js"; +export * from "./helpers.js"; diff --git a/src/models/Location/regex.ts b/src/models/Location/regex.ts deleted file mode 100644 index 92cc33a8..00000000 --- a/src/models/Location/regex.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Regex patterns for validating `Location` components. - * - * Location components are stored separately, despite some having patterns which are - * currently the exact same and therefore duplicative. This is due to the fact that they - * may differ when converted into unicode-based patterns to support i18n. Note that these - * patterns all assume spaces have been replaced with underscores. - */ -export const LOCATION_REGEX_STRS = { - COUNTRY: "[a-z-_]{2,}", // Two or more letters/hyphens/underscores - REGION: "[a-z-_]{2,}", // Two or more letters/hyphens/underscores - CITY: "[a-z-_]{2,}", // Two or more letters/hyphens/underscores - STREET_LINE_1: "[a-z0-9-_.]{2,}", // Two or more letters/hyphens/underscores/numbers/periods - STREET_LINE_2: "[a-z0-9-_.:#]{2,}", // Two or more letters/hyphens/underscores/numbers/periods/:/# -}; - -// Shortened name for interpolation in LOCATION_COMPOSITE_REGEX -const LOC = LOCATION_REGEX_STRS; - -/** - * Location Composite Value Pattern: - * - * `[COUNTRY]#[STATE]#[CITY]#[STREET_LINE_1]#[STREET_LINE_2]` - * - * Regex Pattern: - * - * `^([a-z-_]{2,})(#[a-z-_]{2,})(#[a-z-_]{2,})(#[a-z0-9-_.]{2,})(#[a-z0-9-_.#]{2,})?$` - * - * - Note: location names which use non-latin-alphabetic characters are not yet supported at this time. - * - * // IDEA Add unicode regex patterns to support internationalization (e.g., /[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]/u). - */ -export const LOCATION_COMPOSITE_REGEX = new RegExp( - `^(${LOC.COUNTRY})(#${LOC.REGION})(#${LOC.CITY})(#${LOC.STREET_LINE_1})(#${LOC.STREET_LINE_2})?$`, - "i" // <-- Makes all location field patterns case-insensitive -); diff --git a/src/models/PasswordResetToken/PasswordResetToken.ts b/src/models/PasswordResetToken/PasswordResetToken.ts new file mode 100644 index 00000000..6f77d2c6 --- /dev/null +++ b/src/models/PasswordResetToken/PasswordResetToken.ts @@ -0,0 +1,121 @@ +import { randomBytes } from "crypto"; +import { Model } from "@nerdware/ddb-single-table"; +import { isValidHex } from "@nerdware/ts-string-helpers"; +import dayjs from "dayjs"; +import { userModelHelpers } from "@/models/User/helpers.js"; +import { COMMON_ATTRIBUTES } from "@/models/_common/modelAttributes.js"; +import { ddbTable } from "@/models/ddbTable.js"; +import { passwordResetTokenModelHelpers as pwResetTokenModelHelpers } from "./helpers.js"; +import type { ItemTypeFromSchema, ItemCreationParameters } from "@nerdware/ddb-single-table"; + +/** + * PasswordResetToken Model + * + * > _**Item TTL = 15 minutes**_ + */ +class PasswordResetTokenModel extends Model { + static readonly ENCODING: BufferEncoding = "hex"; + static readonly BYTE_LENGTH: number = 48; + static readonly CHAR_LENGTH: number = PasswordResetTokenModel.BYTE_LENGTH * 2; // 1 hex byte = 2 chars + + static readonly createToken = () => { + return randomBytes(PasswordResetTokenModel.BYTE_LENGTH).toString( + PasswordResetTokenModel.ENCODING + ); + }; + + static readonly schema = ddbTable.getModelSchema({ + pk: { + type: "string", + alias: "token", + default: () => PasswordResetTokenModel.createToken(), + required: true, + }, + sk: { + type: "string", + default: ({ pk: token }: { pk?: string }) => + token ? pwResetTokenModelHelpers.sk.format(token) : undefined, + validate: pwResetTokenModelHelpers.sk.isValid, + required: true, + }, + data: { + type: "string", + default: ({ pk: token, sk }: { pk?: string; sk?: string }) => + sk ? sk : token ? pwResetTokenModelHelpers.sk.format(token) : undefined, + validate: pwResetTokenModelHelpers.data.isValid, + required: true, + }, + userID: { + type: "string", + validate: userModelHelpers.id.isValid, + required: true, + }, + expiresAt: { + ...COMMON_ATTRIBUTES.TTL.expiresAt, + default: () => dayjs().add(15, "minutes").unix(), // expires 15 minutes from now + required: true, + }, + } as const); + + /** + * Returns a boolean indicating whether `token` has the correct encoding and length. + * > Use this for validating raw token strings from the client. + */ + static readonly isRawTokenProperlyEncoded = (token: string) => { + return isValidHex(token) && token.length === PasswordResetTokenModel.CHAR_LENGTH; + }; + + /** + * Returns a boolean indicating whether `token` is valid (i.e., exists and hasn't expired). + * > Use this for checking a token once it has been retrieved from the database. + */ + static readonly isTokenValid = ( + token?: PasswordResetTokenItem + ): token is PasswordResetTokenItem => { + return !!token && dayjs(token.expiresAt).isBefore(dayjs()); + }; + + constructor() { + super("PasswordResetToken", PasswordResetTokenModel.schema, ddbTable); + } + + // PASSWORD RESET TOKEN — Instance methods: + readonly createToken = PasswordResetTokenModel.createToken; + readonly isRawTokenProperlyEncoded = PasswordResetTokenModel.isRawTokenProperlyEncoded; + readonly isTokenValid = PasswordResetTokenModel.isTokenValid; + readonly getFormattedSK = pwResetTokenModelHelpers.sk.format; +} + +/** + * PasswordResetToken Model + * + * > _**Item TTL = 15 minutes**_ + */ +export const PasswordResetToken = new PasswordResetTokenModel(); + +/** The shape of a `PasswordResetToken` object returned from Model methods. */ +export type PasswordResetTokenItem = ItemTypeFromSchema< + typeof PasswordResetTokenModel.schema, + { + aliasKeys: true; + optionalIfDefault: false; + nullableIfOptional: false; + autoAddTimestamps: false; + } +>; + +/** `PasswordResetToken` item params for `createItem()`. */ +export type PasswordResetTokenCreateItemParams = ItemCreationParameters< + typeof PasswordResetTokenModel.schema +>; + +/** The shape of a raw/unaliased `PasswordResetToken` object in the DB. */ +export type UnaliasedPasswordResetTokenItem = ItemTypeFromSchema< + typeof PasswordResetTokenModel.schema, + { + aliasKeys: false; + optionalIfDefault: false; + nullableIfOptional: true; + autoAddTimestamps: false; + } +>; diff --git a/src/models/PasswordResetToken/helpers.ts b/src/models/PasswordResetToken/helpers.ts new file mode 100644 index 00000000..9f008d24 --- /dev/null +++ b/src/models/PasswordResetToken/helpers.ts @@ -0,0 +1,24 @@ +import { + createMapOfStringAttrHelpers, + createHelpersForStrAttr, + getCompoundAttrRegex, + DELIMETER, + type MapOfStringAttrHelpers, +} from "@/models/_common"; + +export const PW_RESET_TOKEN_SK_PREFIX_STR = "PW_RESET_TOKEN"; + +const pwResetTokenSKattrHelpers = createHelpersForStrAttr("sk", { + /** Validation regex for `PasswordResetToken.sk` compound attribute. */ + regex: getCompoundAttrRegex([ + PW_RESET_TOKEN_SK_PREFIX_STR, + /^[a-f0-9]{96}$/, // Regex pattern for 48-bit hex tokens (96 characters long) + ]), + /** PasswordResetToken "sk" value formatter. */ + format: (token: string) => `${PW_RESET_TOKEN_SK_PREFIX_STR}${DELIMETER}${token}`, +}); + +export const passwordResetTokenModelHelpers = createMapOfStringAttrHelpers({ + sk: pwResetTokenSKattrHelpers, + data: pwResetTokenSKattrHelpers, // PRT `data` attribute is currently equal to the `sk` attribute +}) satisfies MapOfStringAttrHelpers; diff --git a/src/models/PasswordResetToken/index.ts b/src/models/PasswordResetToken/index.ts new file mode 100644 index 00000000..4445bb42 --- /dev/null +++ b/src/models/PasswordResetToken/index.ts @@ -0,0 +1,2 @@ +export * from "./PasswordResetToken.js"; +export * from "./helpers.js"; diff --git a/src/models/Profile/Profile.test.ts b/src/models/Profile/Profile.test.ts index 2d8bc14f..5d4a3505 100644 --- a/src/models/Profile/Profile.test.ts +++ b/src/models/Profile/Profile.test.ts @@ -1,7 +1,7 @@ -import { Profile, type ProfileParams } from "./Profile.js"; +import { Profile, type CreateProfileParams } from "./Profile.js"; /** Valid ProfileParams */ -const PROFILE_ARGS: ProfileParams = { +const PROFILE_ARGS: CreateProfileParams = { handle: "@test_handle", displayName: "Person McHumanPerson", givenName: "Person", @@ -44,7 +44,7 @@ describe("Profile", () => { // handle + givenName => givenName expect(Profile.getDisplayName({ handle, givenName })).toBe(givenName); // handle + givenName + familyName => displayName should take the form "givenName familyName" - expect(Profile.getDisplayName({ handle, familyName, givenName })).toBe(`${givenName} ${familyName}`); // prettier-ignore + expect(Profile.getDisplayName({ handle, familyName, givenName })).toBe(`${givenName!} ${familyName!}`); // prettier-ignore // handle + givenName + familyName + bizName => bizName expect(Profile.getDisplayName({ handle, familyName, givenName, businessName })).toBe(businessName); // prettier-ignore // all params with explicit displayName => displayName diff --git a/src/models/Profile/Profile.ts b/src/models/Profile/Profile.ts index 193ff50f..633ca013 100644 --- a/src/models/Profile/Profile.ts +++ b/src/models/Profile/Profile.ts @@ -1,5 +1,5 @@ import type { Profile as GqlSchemaProfileType } from "@/types/graphql.js"; -import type { Simplify } from "type-fest"; +import type { Simplify, SetNonNullable } from "type-fest"; /** * A `Profile` object represents a user's profile information. @@ -47,7 +47,7 @@ export class Profile implements NonNullableProfile { givenName, familyName, businessName, - }: ProfileParams) => { + }: CreateProfileParams) => { return displayName ? displayName : businessName @@ -62,7 +62,7 @@ export class Profile implements NonNullableProfile { /** * Returns a `Profile`-shaped object from the given params. */ - static readonly fromParams = (params: ProfileParams) => new Profile(params); + static readonly fromParams = (params: CreateProfileParams) => new Profile(params); constructor({ handle, @@ -71,7 +71,7 @@ export class Profile implements NonNullableProfile { familyName, businessName, photoUrl, - }: ProfileParams) { + }: CreateProfileParams) { if (givenName) this.givenName = givenName; if (familyName) this.familyName = familyName; if (businessName) this.businessName = businessName; @@ -91,13 +91,11 @@ export class Profile implements NonNullableProfile { * The {@link Profile} class implements this type, which reflects the GraphQL * {@link GqlSchemaProfileType|Profile} type with all fields made `NonNullable`. */ -type NonNullableProfile = { - [Key in keyof GqlSchemaProfileType]: NonNullable; -}; +type NonNullableProfile = SetNonNullable; /** * The parameters that go into creating a new {@link Profile|`Profile`} object. */ -export type ProfileParams = Simplify<{ +export type CreateProfileParams = Simplify<{ [Key in "handle" | keyof GqlSchemaProfileType]?: string | null | undefined; }>; diff --git a/src/models/Profile/helpers.ts b/src/models/Profile/helpers.ts new file mode 100644 index 00000000..f0411f93 --- /dev/null +++ b/src/models/Profile/helpers.ts @@ -0,0 +1,31 @@ +import { isValidHandle, isValidURL, isValidName } from "@nerdware/ts-string-helpers"; +import { isString } from "@nerdware/ts-type-safety-utils"; + +/** + * Sanitizes a `displayName` value by removing any characters that are not permitted. + * + * Permitted Characters: + * - `givenName`/`familyName`/`businessName` characters: + * - `\p{Script=Latin}`, white space, apostrophes, periods, and hyphens + * - `handle` characters: + * - `0-9`, `@`, and `_` + */ +export const sanitizeDisplayName = (displayName?: string | null | undefined) => { + return displayName ? displayName.trim().replace(/[^\s\p{Script=Latin}0-9@_'.-]/giu, "") : ""; +}; + +/** + * Returns `true` if `displayName` is a valid `Profile` name value or a valid `handle`. + */ +export const isValidDisplayName = (displayName: string) => { + return isValidName(displayName) || isValidHandle(displayName); +}; + +/** + * Returns `true` if `value` is a valid absolute http/https/s3 URL for `Profile.photoUrl`. + */ +export const isValidProfilePhotoUrl = (value?: unknown) => { + return isString(value) + ? isValidURL(value) && /^(http(s)?|s3):\/\//.test(value) + : value === null || value === undefined; +}; diff --git a/src/models/README.md b/src/models/README.md new file mode 100644 index 00000000..d3c99ff3 --- /dev/null +++ b/src/models/README.md @@ -0,0 +1,11 @@ +# Models + +This directory contains the application's `models`, which are created using [`@nerdware/ddb-single-table`](https://www.npmjs.com/package/@nerdware/ddb-single-table). + +### Model Design Goals + +Each `model` is designed to achieve the following design goals: + +- Define the shape of database objects. +- Encapsulate database CRUD operations. +- Handle all database I/O validation and transformation. diff --git a/src/models/User/User.test.ts b/src/models/User/User.test.ts index 7cf7f792..c1662153 100644 --- a/src/models/User/User.test.ts +++ b/src/models/User/User.test.ts @@ -1,5 +1,3 @@ -import { isValidStripeID } from "@/lib/stripe/isValidStripeID.js"; -import { userStripeConnectAccountModelHelpers as scaModelHelpers } from "@/models/UserStripeConnectAccount/helpers.js"; import { MOCK_USERS, UNALIASED_MOCK_USERS } from "@/tests/staticMockItems/users.js"; import { User } from "./User.js"; import { userModelHelpers } from "./helpers.js"; @@ -7,49 +5,35 @@ import { userModelHelpers } from "./helpers.js"; const { USER_A, USER_B, USER_C } = MOCK_USERS; describe("User Model", () => { - describe("User.createOne()", () => { + describe("User.createItem()", () => { test("returns a valid User when called with valid arguments", async () => { // Arrange mock Users for (const key in MOCK_USERS) { - // Get input for User.createOne() method - const mockUser = MOCK_USERS[key as keyof typeof MOCK_USERS]; - const input = { - ...mockUser, - ...(mockUser.login.type === "LOCAL" ? { password: "MockPassword@123" } : mockUser.login), - }; + // Get input for User.createItem() method + const input = MOCK_USERS[key as keyof typeof MOCK_USERS]; - // Act on the User.createOne() method - const result = await User.createOne(input); + // Act on the User.createItem() method + const result = await User.createItem(input); // Assert the result expect(result).toStrictEqual({ id: expect.toSatisfyFn((value) => userModelHelpers.id.isValid(value)), sk: expect.toSatisfyFn((value) => userModelHelpers.sk.isValid(value)), - handle: mockUser.handle, - email: mockUser.email, - phone: mockUser.phone, - stripeCustomerID: mockUser.stripeCustomerID, - ...(mockUser.expoPushToken && { expoPushToken: mockUser.expoPushToken }), + handle: input.handle, + email: input.email, + phone: input.phone, + stripeCustomerID: input.stripeCustomerID, + expoPushToken: input.expoPushToken, profile: { - ...mockUser.profile, + ...input.profile, givenName: expect.toBeOneOf([undefined, null, expect.any(String)]), familyName: expect.toBeOneOf([undefined, null, expect.any(String)]), businessName: expect.toBeOneOf([undefined, null, expect.any(String)]), photoUrl: expect.toBeOneOf([undefined, null, expect.any(String)]), }, login: { - ...mockUser.login, - ...(mockUser.login.type === "LOCAL" && { passwordHash: expect.any(String) }), - }, - stripeConnectAccount: { - userID: expect.toSatisfyFn((value) => userModelHelpers.id.isValid(value)), - id: expect.toSatisfyFn((value) => isValidStripeID.connectAccount(value)), - sk: expect.toSatisfyFn((value) => scaModelHelpers.sk.isValid(value)), - detailsSubmitted: expect.any(Boolean), - chargesEnabled: expect.any(Boolean), - payoutsEnabled: expect.any(Boolean), - createdAt: expect.any(Date), - updatedAt: expect.any(Date), + ...input.login, + ...(input.login.type === "LOCAL" && { passwordHash: expect.any(String) }), }, createdAt: expect.any(Date), updatedAt: expect.any(Date), diff --git a/src/models/User/User.ts b/src/models/User/User.ts index a158629e..4c65b33b 100644 --- a/src/models/User/User.ts +++ b/src/models/User/User.ts @@ -1,123 +1,115 @@ -import { Model } from "@nerdware/ddb-single-table"; -import { isValidEmail, isValidHandle } from "@nerdware/ts-string-helpers"; +import { isValidEmail, isValidHandle, isValidName } from "@nerdware/ts-string-helpers"; import { hasKey, isPlainObject } from "@nerdware/ts-type-safety-utils"; import { Expo } from "expo-server-sdk"; -import { isValidStripeID } from "@/lib/stripe/isValidStripeID.js"; +import { isValidStripeID } from "@/lib/stripe/helpers.js"; +import { isValidDisplayName, isValidProfilePhotoUrl } from "@/models/Profile/helpers.js"; import { COMMON_ATTRIBUTE_TYPES, COMMON_ATTRIBUTES } from "@/models/_common/modelAttributes.js"; import { ddbTable } from "@/models/ddbTable.js"; -import { createOne } from "./createOne.js"; import { userModelHelpers } from "./helpers.js"; -import type { UserLoginU } from "@/models/UserLogin/UserLogin.js"; +import type { UserLoginObject } from "@/models/UserLogin"; import type { ItemTypeFromSchema, ItemCreationParameters } from "@nerdware/ddb-single-table"; import type { OverrideProperties } from "type-fest"; /** - * User Model + * User Model Schema */ -class UserModel extends Model { - static readonly schema = ddbTable.getModelSchema({ - pk: { - type: "string", - alias: "id", - default: ({ createdAt }: { createdAt: Date }) => userModelHelpers.id.format(createdAt), - validate: userModelHelpers.id.isValid, - required: true, - }, - sk: { - type: "string", - default: (userItem: { pk: string }) => - userItem?.pk ? userModelHelpers.sk.format(userItem.pk) : undefined, - validate: userModelHelpers.sk.isValid, - required: true, - }, - data: { - type: "string", - alias: "email", - validate: (value: string) => isValidEmail(value), - required: true, - }, - handle: { - type: "string", - validate: (value: string) => isValidHandle(value), - required: true, // NOTE: User.handle is case-sensitive +const userModelSchema = ddbTable.getModelSchema({ + pk: { + type: "string", + alias: "id", + default: ({ handle }: { handle: string }) => userModelHelpers.id.format(handle), + validate: userModelHelpers.id.isValid, + required: true, + }, + sk: { + type: "string", + default: (userItem: { pk: string }) => + userItem.pk ? userModelHelpers.sk.format(userItem.pk) : undefined, + validate: userModelHelpers.sk.isValid, + required: true, + }, + data: { + type: "string", + alias: "email", + validate: isValidEmail, + required: true, + transformValue: { + toDB: (email: string) => email.toLowerCase(), }, - phone: { - ...COMMON_ATTRIBUTE_TYPES.PHONE, - required: false, + }, + handle: { + type: "string", + validate: isValidHandle, + required: true, // NOTE: User.handle is case-sensitive + }, + phone: { + ...COMMON_ATTRIBUTE_TYPES.PHONE, + required: false, + }, + expoPushToken: { + type: "string", + validate: (tokenValue: string) => Expo.isExpoPushToken(tokenValue), + required: false, + }, + stripeCustomerID: { + type: "string", + validate: isValidStripeID.customer, + required: true, + }, + login: { + type: "map", + required: true, + schema: { + // login type + type: { type: "enum", oneOf: ["LOCAL", "GOOGLE_OAUTH"], required: true }, + // LOCAL login properties: + passwordHash: { type: "string" }, + // GOOGLE_OAUTH login properties: + googleID: { type: "string" }, }, - expoPushToken: { - type: "string", // The push-service may set EPT to empty string - validate: (tokenValue: string) => tokenValue === "" || Expo.isExpoPushToken(tokenValue), - required: false, + validate: (login: unknown) => + isPlainObject(login) && + hasKey(login, "type") && + (login.type === "LOCAL" + ? hasKey(login, "passwordHash") + : login.type === "GOOGLE_OAUTH" + ? hasKey(login, "googleID") + : false), + }, + profile: { + type: "map", + required: true, + schema: { + displayName: { type: "string", required: true, validate: isValidDisplayName }, + givenName: { type: "string", validate: isValidName }, + familyName: { type: "string", validate: isValidName }, + businessName: { type: "string", validate: isValidName }, + photoUrl: { type: "string", validate: isValidProfilePhotoUrl }, }, - stripeCustomerID: { - type: "string", - validate: (value: string) => isValidStripeID.customer(value), - required: true, - }, - login: { - type: "map", - required: true, - schema: { - // login type - type: { type: "enum", oneOf: ["LOCAL", "GOOGLE_OAUTH"], required: true }, - // LOCAL login properties: - passwordHash: { type: "string" }, - // GOOGLE_OAUTH login properties: - googleID: { type: "string" }, - }, - validate: (login: unknown) => - isPlainObject(login) && - hasKey(login, "type") && - (login.type === "LOCAL" - ? hasKey(login, "passwordHash") - : login.type === "GOOGLE_OAUTH" - ? hasKey(login, "googleID") - : false), - }, - profile: { - type: "map", - required: true, - schema: { - displayName: { type: "string", required: true }, - givenName: { type: "string" }, - familyName: { type: "string" }, - businessName: { type: "string" }, - photoUrl: { type: "string" }, - }, - }, - ...COMMON_ATTRIBUTES.TIMESTAMPS, // "createdAt" and "updatedAt" timestamps - } as const); + }, + ...COMMON_ATTRIBUTES.TIMESTAMPS, // "createdAt" and "updatedAt" timestamps +} as const); - constructor() { - super("User", UserModel.schema, ddbTable); - } - - // USER MODEL — Instance methods: - readonly getFormattedSK = userModelHelpers.sk.format; - readonly createOne = createOne; -} - -export const User = new UserModel(); +/** + * User Model + */ +export const User = ddbTable.createModel("User", userModelSchema); -/** The shape of a `User` object returned from UserModel methods. */ +/** The shape of a `User` object returned from Model methods. */ export type UserItem = OverrideProperties< - ItemTypeFromSchema, - { login: UserLoginU } + ItemTypeFromSchema, + { phone: string | null; login: UserLoginObject } >; /** `User` item params for `createItem()`. */ -export type UserItemCreationParams = OverrideProperties< - ItemCreationParameters, - { login: UserLoginU } +export type UserCreateItemParams = OverrideProperties< + ItemCreationParameters, + { phone: string | null; login: UserLoginObject } >; -/** - * The shape of a `User` object in the DB. - * > This type is used to mock `@aws-sdk/lib-dynamodb` responses. - */ +/** The shape of a raw/unaliased `User` object in the DB. */ export type UnaliasedUserItem = ItemTypeFromSchema< - typeof UserModel.schema, + typeof userModelSchema, { aliasKeys: false; optionalIfDefault: false; diff --git a/src/models/User/createOne.ts b/src/models/User/createOne.ts deleted file mode 100644 index d0aa7937..00000000 --- a/src/models/User/createOne.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { usersCache } from "@/lib/cache/usersCache.js"; -import { stripe } from "@/lib/stripe/stripeClient.js"; -import { Profile } from "@/models/Profile/Profile.js"; -import { UserLogin, type CreateLoginParams } from "@/models/UserLogin/UserLogin.js"; -import { - UserStripeConnectAccount, - type UserStripeConnectAccountItem, -} from "@/models/UserStripeConnectAccount/UserStripeConnectAccount.js"; -import { logger } from "@/utils/logger.js"; -import type { UserItem, User } from "@/models/User/User.js"; -import type { Simplify, SetOptional } from "type-fest"; - -/** `User.createOne()` method params. */ -export type UserCreateOneParams = Simplify< - CreateLoginParams & - SetOptional< - Pick, - "profile" - > ->; - -/** - * `User.createOne` creates the following items: - * - `User` (created in the DB) - * - `UserStripeConnectAccount` (created in the DB AND Stripe's API) - */ -export const createOne = async function ( - this: typeof User, - { - handle, - email, - phone = null, - expoPushToken, // Only mobile-app users will have this - password, // Only local logins will have this - profile, // Only Google OAuth logins will have this at reg-time - googleID, // Only Google OAuth logins will have this - }: UserCreateOneParams -) { - let newUser: UserItem; - let newUserStripeConnectAccount: UserStripeConnectAccountItem; - - // Create Profile object - const newUserProfile = Profile.fromParams({ handle, ...(profile ?? {}) }); - - // Create Stripe Customer via Stripe API - const { id: stripeCustomerID } = await stripe.customers.create({ - email, - ...(phone && { phone }), - ...(newUserProfile.displayName.length > 0 && { name: newUserProfile.displayName }), - }); - - // Var the catch block can use to "undo" created resources if an error is thrown: - let newUserID: string | undefined; - let wasUserScaCreated = false; - - try { - // Create User - newUser = await this.createItem({ - handle, - email, - phone, - ...(expoPushToken && { expoPushToken }), - stripeCustomerID, - profile: { ...newUserProfile }, - login: await UserLogin.createLogin({ password, googleID }), - }); - - newUserID = newUser.id; - - // Create newUser's Stripe Connect Account - newUserStripeConnectAccount = await UserStripeConnectAccount.createOne({ - userID: newUser.id, - email: newUser.email, - phone: newUser.phone, - profile: newUser.profile, - }); - - wasUserScaCreated = true; - - // Add newUser to usersCache for search-by-handle - usersCache.set(newUser.handle, { - id: newUser.id, - handle: newUser.handle, - email: newUser.email, - phone: newUser.phone ?? null, - profile: newUser.profile, - createdAt: newUser.createdAt, - updatedAt: newUser.updatedAt, - }); - } catch (error) { - // Log the error, and delete the Stripe Customer on fail - logger.error(error, "User.createOne"); - - // Delete the Stripe Customer: - - logger.stripe( - `Failed to create User with email "${email}". Deleting Stripe Customer "${stripeCustomerID}"...` - ); - - await stripe.customers.del(stripeCustomerID); - - logger.stripe(`Deleted Stripe Customer "${stripeCustomerID}".`); - - // Delete the User, if it was created: - if (newUserID) { - logger.info(`Deleting User "${newUserID}" due to failed User.createOne()...`); - this.deleteItem({ id: newUserID }) - .then(() => logger.info(`SUCCESS: Deleted User "${newUserID}"`)) - .catch(() => logger.error(`ERROR: FAILED TO DELETE User "${newUserID}"`)); - - // Delete the UserStripeConnectAccount, if it was created: - if (wasUserScaCreated) { - logger.info(`Deleting SCA of User "${newUserID}" due to failed User.createOne()...`); - UserStripeConnectAccount.deleteItem({ userID: newUserID }) - .then(() => logger.info(`SUCCESS: Deleted SCA of User "${newUserID}"`)) - .catch(() => logger.error(`ERROR: FAILED TO DELETE SCA of User "${newUserID}"`)); - } - } - - // Re-throw to allow MW to handle the error sent to the user - throw error; - } - - // If no error was thrown, newUser and SCA were created successfully - return { - ...newUser, - stripeConnectAccount: newUserStripeConnectAccount, - }; -}; diff --git a/src/models/User/helpers.ts b/src/models/User/helpers.ts index 7179ecda..c4f3c3d6 100644 --- a/src/models/User/helpers.ts +++ b/src/models/User/helpers.ts @@ -1,39 +1,29 @@ -import { isDate } from "@nerdware/ts-type-safety-utils"; -import { createModelHelpers } from "@/models/_common/modelHelpers.js"; -import { getUnixTimestampUUID } from "@/utils/uuid.js"; -import { - USER_ID_PREFIX_STR as ID_PREFIX, - USER_ID_REGEX, - USER_SK_PREFIX_STR as SK_PREFIX, - USER_SK_REGEX, -} from "./regex.js"; +import { isValidHandle } from "@nerdware/ts-string-helpers"; +import { createMapOfStringAttrHelpers, getCompoundAttrRegex, DELIMETER } from "@/models/_common"; -export const userModelHelpers = createModelHelpers({ - id: { - regex: USER_ID_REGEX, +export const USER_ID_PREFIX_STR = "USER"; +export const USER_SK_PREFIX_STR = `${DELIMETER}DATA`; - /** - * User "id" value formatter. - * - * @param {Date|string} createdAt - The User's "createdAt" timestamp value represented as - * either a Date object or unix timestamp UUID string. If provided as a Date object, it will - * be converted to a Unix timestamp UUID string. - * - * @returns {string} A formatted User "id" value (alias for "pk" attribute). - */ - format: (createdAt: Date | string) => { - return `${ID_PREFIX}#${isDate(createdAt) ? getUnixTimestampUUID(createdAt) : createdAt}`; - }, +export const userModelHelpers = createMapOfStringAttrHelpers({ + id: { + /** Validation regex for User IDs. */ + regex: getCompoundAttrRegex([USER_ID_PREFIX_STR, isValidHandle._regex]), + /** User "id" value formatter. */ + format: (handle: string) => `${USER_ID_PREFIX_STR}${DELIMETER}${handle}`, + /** Sanitizes a User ID value (permits `handle` chars and the {@link DELIMETER} char). */ + sanitize: (str: string) => str.replace(/[^a-zA-Z0-9_@#]/g, ""), }, sk: { - regex: USER_SK_REGEX, - - /** - * User "sk" value formatter. - * - * @param {string} userID - The User's "id". - * @returns {string} A formatted User "sk" attribute value. - */ - format: (userID: string) => `${SK_PREFIX}#${userID}`, + /** Validation regex for User `sk` attribute values. */ + regex: getCompoundAttrRegex([USER_SK_PREFIX_STR, USER_ID_PREFIX_STR, isValidHandle._regex]), + /** User "sk" value formatter. */ + format: (userID: string) => `${USER_SK_PREFIX_STR}${DELIMETER}${userID}`, }, }); + +/** + * User Model helper fn which extracts a User's `handle` from their `id`. + */ +export const extractHandleFromUserID = (userID: string): string => { + return userID.substring(USER_ID_PREFIX_STR.length + 1); +}; diff --git a/src/models/User/index.ts b/src/models/User/index.ts index 169437c2..5b78a528 100644 --- a/src/models/User/index.ts +++ b/src/models/User/index.ts @@ -1 +1,2 @@ export * from "./User.js"; +export * from "./helpers.js"; diff --git a/src/models/User/regex.ts b/src/models/User/regex.ts deleted file mode 100644 index 09b31182..00000000 --- a/src/models/User/regex.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { UUID_V1_REGEX_STR } from "@/utils/regex.js"; - -export const USER_ID_PREFIX_STR = "USER"; -export const USER_ID_REGEX_STR = `${USER_ID_PREFIX_STR}#${UUID_V1_REGEX_STR}`; -export const USER_ID_REGEX = new RegExp(`^${USER_ID_REGEX_STR}$`); - -export const USER_SK_PREFIX_STR = "#DATA"; -export const USER_SK_REGEX = new RegExp(`^${USER_SK_PREFIX_STR}#${USER_ID_REGEX_STR}$`); diff --git a/src/models/UserLogin/UserLogin.test.ts b/src/models/UserLogin/UserLogin.test.ts index 5ab241ee..5efec14f 100644 --- a/src/models/UserLogin/UserLogin.test.ts +++ b/src/models/UserLogin/UserLogin.test.ts @@ -6,14 +6,14 @@ describe("UserLogin", () => { const password = "MockPassword@123"; const result = await UserLogin.createLogin({ password }); expect(result.type).toBe("LOCAL"); - expect(result.passwordHash).toBeTypeOf("string"); + expect((result as any).passwordHash).toBeTypeOf("string"); }); test("returns a GOOGLE_OAUTH UserLogin when called with a Google ID and access token", async () => { const googleID = "gid_123"; const result = await UserLogin.createLogin({ googleID }); expect(result.type).toBe("GOOGLE_OAUTH"); - expect(result.googleID).toBe(googleID); + expect((result as any).googleID).toBe(googleID); }); test("throws an error when called without any params", async () => { diff --git a/src/models/UserLogin/UserLogin.ts b/src/models/UserLogin/UserLogin.ts index 77e20ef5..91061615 100644 --- a/src/models/UserLogin/UserLogin.ts +++ b/src/models/UserLogin/UserLogin.ts @@ -1,6 +1,7 @@ import { isValidPassword } from "@nerdware/ts-string-helpers"; import { isString } from "@nerdware/ts-type-safety-utils"; import { passwordHasher } from "@/utils/passwordHasher.js"; +import type { CombineUnionOfObjects } from "@/types/helpers.js"; import type { Simplify } from "type-fest"; /** @@ -19,10 +20,10 @@ export class UserLogin { * @returns A promise that resolves to the created UserLogin object. * @throws Error if the provided parameters are invalid. */ - public static readonly createLogin = async ({ + public static readonly createLogin = async ({ password, googleID, - }: Params) => { + }: LoginParams): Promise => { // Run the appropriate method based on the provided params const userLogin = isString(password) ? await UserLogin.createLoginLocal(password) @@ -33,11 +34,7 @@ export class UserLogin { // Ensure that the userLogin object is not null: if (!userLogin) throw new Error("Invalid login credentials"); - return userLogin as Params extends { password: string } - ? UserLoginLocal - : Params extends { googleID: string } - ? UserLoginGoogleOAuth - : never; + return userLogin; }; /** @@ -76,16 +73,21 @@ export class UserLogin { // LOGIN TYPES: /** - * Parameters for creating a `UserLogin` object. + * `UserLogin` params */ -export type CreateLoginParams = { - password?: string | undefined; // <-- UserLogin class currently hashes passwords - googleID?: string | undefined; // <-- googleIDToken is processed by upstream mw -}; // TODO Consider mv'ind gidToken here, or pw elsewhere +export type LoginParams = { + password?: string | undefined; + googleID?: string | undefined; +}; /** - * A union of `UserLogin` object types, discriminated by the - * {@link FixitApiLoginAuthType|`type` string literal property}. + * A combination of every `UserLogin` object type in the {@link UserLoginU} union. + */ +export type UserLoginObject = CombineUnionOfObjects; + +/** + * A union of `UserLogin` object types, discriminated by the {@link FixitApiLoginAuthType|`type` string literal property}. + * > Use this `UserLogin` type when you want type inference based on the `type` property. */ export type UserLoginU = UserLoginLocal | UserLoginGoogleOAuth; diff --git a/src/models/UserStripeConnectAccount/UserStripeConnectAccount.test.ts b/src/models/UserStripeConnectAccount/UserStripeConnectAccount.test.ts index c5257484..c66fc046 100644 --- a/src/models/UserStripeConnectAccount/UserStripeConnectAccount.test.ts +++ b/src/models/UserStripeConnectAccount/UserStripeConnectAccount.test.ts @@ -2,27 +2,31 @@ import { MOCK_USER_SCAs, UNALIASED_MOCK_USER_SCAs, } from "@/tests/staticMockItems/userStripeConnectAccounts.js"; -import { MOCK_USERS } from "@/tests/staticMockItems/users.js"; import { UserStripeConnectAccount } from "./UserStripeConnectAccount.js"; describe("UserStripeConnectAccount Model", () => { - describe("UserStripeConnectAccount.createOne()", () => { + describe("UserStripeConnectAccount.createItem()", () => { test("returns a valid UserStripeConnectAccount when called with valid args", async () => { - // Act on the UserStripeConnectAccount Model's createOne method - const result = await UserStripeConnectAccount.createOne({ - userID: MOCK_USER_SCAs.SCA_A.userID, - ...MOCK_USERS.USER_A, - }); + // Arrange mock UserSCA inputs + for (const key in MOCK_USER_SCAs) { + // Get input for UserStripeConnectAccount.createItem() method + const input = MOCK_USER_SCAs[key as keyof typeof MOCK_USER_SCAs]; - // Assert the result - expect(result).toStrictEqual({ - ...MOCK_USER_SCAs.SCA_A, - createdAt: expect.any(Date), - updatedAt: expect.any(Date), - }); + // Act on the UserStripeConnectAccount Model's createItem method + const result = await UserStripeConnectAccount.createItem(input); + + // Assert the result + expect(result).toStrictEqual({ + ...input, + createdAt: expect.any(Date), + updatedAt: expect.any(Date), + }); + } }); }); + // TODO Make test for UserStripeConnectAccount.updateItem() + describe("UserStripeConnectAccount.deleteItem()", () => { test(`returns a deleted UserStripeConnectAccount's "userID"`, async () => { // Arrange spy on UserStripeConnectAccount.ddbClient.deleteItem() method diff --git a/src/models/UserStripeConnectAccount/UserStripeConnectAccount.ts b/src/models/UserStripeConnectAccount/UserStripeConnectAccount.ts index d2a56f98..ebb0645a 100644 --- a/src/models/UserStripeConnectAccount/UserStripeConnectAccount.ts +++ b/src/models/UserStripeConnectAccount/UserStripeConnectAccount.ts @@ -1,75 +1,69 @@ -import { Model } from "@nerdware/ddb-single-table"; -import { isValidStripeID } from "@/lib/stripe/isValidStripeID.js"; +import { isValidStripeID } from "@/lib/stripe/helpers.js"; import { userModelHelpers } from "@/models/User/helpers.js"; import { COMMON_ATTRIBUTES } from "@/models/_common/modelAttributes.js"; import { ddbTable } from "@/models/ddbTable.js"; -import { createOne } from "./createOne.js"; -import { userStripeConnectAccountModelHelpers as scaModelHelpers } from "./helpers.js"; -import { STRIPE_CONNECT_ACCOUNT_SK_PREFIX_STR } from "./regex.js"; -import type { ItemTypeFromSchema } from "@nerdware/ddb-single-table"; +import { scaModelHelpers } from "./helpers.js"; +import type { ItemTypeFromSchema, ItemCreationParameters } from "@nerdware/ddb-single-table"; /** - * UserStripeConnectAccount Model + * UserStripeConnectAccount Model Schema */ -class UserStripeConnectAccountModel extends Model { - static readonly schema = ddbTable.getModelSchema({ - pk: { - type: "string", - required: true, - alias: "userID", - validate: userModelHelpers.id.isValid, - }, - sk: { - type: "string", - default: (userSCA: { pk?: string }) => - userSCA?.pk ? scaModelHelpers.sk.format(userSCA.pk) : undefined, - validate: scaModelHelpers.sk.isValid, - required: true, - }, - data: { - type: "string", - alias: "id", - validate: (value: string) => isValidStripeID.connectAccount(value), - required: true, - }, - detailsSubmitted: { - type: "boolean", - required: true, - }, - chargesEnabled: { - type: "boolean", - required: true, - }, - payoutsEnabled: { - type: "boolean", - required: true, - }, - ...COMMON_ATTRIBUTES.TIMESTAMPS, // "createdAt" and "updatedAt" timestamps - } as const); - - constructor() { - super("UserStripeConnectAccount", UserStripeConnectAccountModel.schema, ddbTable); - } +const userStripeConnectAccountModelSchema = ddbTable.getModelSchema({ + pk: { + type: "string", + required: true, + alias: "userID", + validate: userModelHelpers.id.isValid, + }, + sk: { + type: "string", + default: (userSCA: { pk?: string }) => + userSCA.pk ? scaModelHelpers.sk.format(userSCA.pk) : undefined, + validate: scaModelHelpers.sk.isValid, + required: true, + }, + data: { + type: "string", + alias: "id", + validate: (value: string) => isValidStripeID.connectAccount(value), + required: true, + }, + detailsSubmitted: { + type: "boolean", + required: true, + }, + chargesEnabled: { + type: "boolean", + required: true, + }, + payoutsEnabled: { + type: "boolean", + required: true, + }, + ...COMMON_ATTRIBUTES.TIMESTAMPS, // "createdAt" and "updatedAt" timestamps +} as const); - // USER STRIPE CONNECT ACCOUNT MODEL — Instance properties and methods: - readonly SK_PREFIX = STRIPE_CONNECT_ACCOUNT_SK_PREFIX_STR; - readonly getFormattedSK = scaModelHelpers.sk.format; - readonly createOne = createOne; -} - -export const UserStripeConnectAccount = new UserStripeConnectAccountModel(); +/** + * UserStripeConnectAccount Model + */ +export const UserStripeConnectAccount = ddbTable.createModel( + "UserStripeConnectAccount", + userStripeConnectAccountModelSchema +); /** The shape of a `UserStripeConnectAccount` object returned from Model methods. */ export type UserStripeConnectAccountItem = ItemTypeFromSchema< - typeof UserStripeConnectAccountModel.schema + typeof userStripeConnectAccountModelSchema >; -/** - * The shape of a `UserStripeConnectAccount` object in the DB. - * > This type is used to mock `@aws-sdk/lib-dynamodb` responses. - */ +/** `UserStripeConnectAccount` item params for `createItem()`. */ +export type UserStripeConnectAccountCreateItemParams = ItemCreationParameters< + typeof userStripeConnectAccountModelSchema +>; + +/** The shape of a raw/unaliased `UserStripeConnectAccount` object in the DB. */ export type UnaliasedUserStripeConnectAccountItem = ItemTypeFromSchema< - typeof UserStripeConnectAccountModel.schema, + typeof userStripeConnectAccountModelSchema, { aliasKeys: false; optionalIfDefault: false; diff --git a/src/models/UserStripeConnectAccount/helpers.ts b/src/models/UserStripeConnectAccount/helpers.ts index 819bda99..12432b88 100644 --- a/src/models/UserStripeConnectAccount/helpers.ts +++ b/src/models/UserStripeConnectAccount/helpers.ts @@ -1,13 +1,13 @@ -import { createModelHelpers } from "@/models/_common/modelHelpers.js"; -import { - STRIPE_CONNECT_ACCOUNT_SK_PREFIX_STR as SCA_SK_PREFIX, - STRIPE_CONNECT_ACCOUNT_SK_REGEX as SCA_SK_REGEX, -} from "./regex.js"; +import { userModelHelpers } from "@/models/User/helpers.js"; +import { createMapOfStringAttrHelpers, getCompoundAttrRegex, DELIMETER } from "@/models/_common"; -export const userStripeConnectAccountModelHelpers = createModelHelpers({ +export const SCA_SK_PREFIX_STR = "STRIPE_CONNECT_ACCOUNT"; + +export const scaModelHelpers = createMapOfStringAttrHelpers({ sk: { - regex: SCA_SK_REGEX, - /** Returns a formatted UserStripeConnectAccount "sk" value */ - format: (userID: string) => `${SCA_SK_PREFIX}#${userID}`, + /** Validation regex for `UserStripeConnectAccount.sk` values. */ + regex: getCompoundAttrRegex([SCA_SK_PREFIX_STR, userModelHelpers.id.regex]), + /** Returns a formatted `UserStripeConnectAccount.sk` value. */ + format: (userID: string) => `${SCA_SK_PREFIX_STR}${DELIMETER}${userID}`, }, }); diff --git a/src/models/UserStripeConnectAccount/index.ts b/src/models/UserStripeConnectAccount/index.ts index 7c8ec504..6a69f78e 100644 --- a/src/models/UserStripeConnectAccount/index.ts +++ b/src/models/UserStripeConnectAccount/index.ts @@ -1 +1,2 @@ export * from "./UserStripeConnectAccount.js"; +export * from "./helpers.js"; diff --git a/src/models/UserStripeConnectAccount/regex.ts b/src/models/UserStripeConnectAccount/regex.ts deleted file mode 100644 index 51878946..00000000 --- a/src/models/UserStripeConnectAccount/regex.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { USER_ID_REGEX_STR } from "@/models/User/regex.js"; - -export const STRIPE_CONNECT_ACCOUNT_SK_PREFIX_STR = "STRIPE_CONNECT_ACCOUNT"; -export const STRIPE_CONNECT_ACCOUNT_SK_REGEX_STR = `^${STRIPE_CONNECT_ACCOUNT_SK_PREFIX_STR}#${USER_ID_REGEX_STR}$`; -export const STRIPE_CONNECT_ACCOUNT_SK_REGEX = new RegExp(STRIPE_CONNECT_ACCOUNT_SK_REGEX_STR); diff --git a/src/models/UserSubscription/UserSubscription.test.ts b/src/models/UserSubscription/UserSubscription.test.ts index 6600dbc8..1f03b840 100644 --- a/src/models/UserSubscription/UserSubscription.test.ts +++ b/src/models/UserSubscription/UserSubscription.test.ts @@ -4,14 +4,14 @@ import { } from "@/tests/staticMockItems/userSubscriptions.js"; import { MOCK_USERS } from "@/tests/staticMockItems/users.js"; import { UserSubscription } from "./UserSubscription.js"; -import { userSubscriptionModelHelpers as subModelHelpers } from "./helpers.js"; +import { subModelHelpers } from "./helpers.js"; describe("UserSubscription Model", () => { - describe("UserSubscription.upsertOne()", () => { + describe("UserSubscription.upsertItem()", () => { test("returns a valid UserSubscription when called with valid arguments", async () => { // Arrange mock UserSubscriptions for (const key in MOCK_USER_SUBS) { - // Get upsertOne inputs from mock UserSub + // Get upsertItem inputs from mock UserSub const mockSub = MOCK_USER_SUBS[key as keyof typeof MOCK_USER_SUBS]; // Ascertain the mock User associated with this mock UserSub @@ -22,28 +22,24 @@ describe("UserSubscription Model", () => { ? MOCK_USERS.USER_B : MOCK_USERS.USER_C; - // Act on the UserSubscription Model's upsertOne method (check sub name AND priceID): + // Act on the UserSubscription Model's upsertItem method: - const result_withSubName = await UserSubscription.upsertOne({ - user: associatedMockUser, - selectedSubscription: mockSub.priceID.split("price_Test")[1] as any, - // all mock sub priceIDs are prefixed with "price_Test" (e.g., "price_TestANNUAL") - }); - const result_withPriceID = await UserSubscription.upsertOne({ - user: associatedMockUser, + const result = await UserSubscription.upsertItem({ + userID: associatedMockUser.id, + id: mockSub.id, + currentPeriodEnd: mockSub.currentPeriodEnd, + productID: mockSub.productID, priceID: mockSub.priceID, + status: mockSub.status, + // all mock sub priceIDs are prefixed with "price_Test" (e.g., "price_TestANNUAL") }); // Assert the results - [result_withSubName, result_withPriceID].forEach((result) => { - /* toMatchObject is used because the upsertOne method's return-value includes fields - returned from the Stripe API that are not in the Model (e.g., "latest_invoice"). */ - expect(result).toMatchObject({ - ...mockSub, - sk: expect.toSatisfyFn((value: string) => subModelHelpers.sk.isValid(value)), - createdAt: expect.any(Date), - updatedAt: expect.any(Date), - }); + expect(result).toStrictEqual({ + ...mockSub, + sk: expect.toSatisfyFn((value: string) => subModelHelpers.sk.isValid(value)), + createdAt: expect.any(Date), + updatedAt: expect.any(Date), }); } }); @@ -123,33 +119,4 @@ describe("UserSubscription Model", () => { }); }); }); - - describe("UserSubscription.validateExisting()", () => { - const YEAR_2000 = new Date(2000, 0); - const YEAR_9999 = new Date(9999, 0); - - test(`does not throw when called with a valid "active" subscription`, () => { - expect(() => { - UserSubscription.validateExisting({ status: "active", currentPeriodEnd: YEAR_9999 }); - }).not.toThrow(); - }); - - test(`does not throw when called with a valid "trialing" subscription`, () => { - expect(() => { - UserSubscription.validateExisting({ status: "trialing", currentPeriodEnd: YEAR_9999 }); - }).not.toThrow(); - }); - - test(`throws an error when called with a subscription with an invalid status`, () => { - expect(() => { - UserSubscription.validateExisting({ status: "past_due", currentPeriodEnd: YEAR_9999 }); - }).toThrow("past due"); - }); - - test(`throws an error when called with an expired subscription`, () => { - expect(() => { - UserSubscription.validateExisting({ status: "active", currentPeriodEnd: YEAR_2000 }); - }).toThrow("expired"); - }); - }); }); diff --git a/src/models/UserSubscription/UserSubscription.ts b/src/models/UserSubscription/UserSubscription.ts index e2ddc6fc..7e009f19 100644 --- a/src/models/UserSubscription/UserSubscription.ts +++ b/src/models/UserSubscription/UserSubscription.ts @@ -2,17 +2,17 @@ import { Model } from "@nerdware/ddb-single-table"; import { hasKey } from "@nerdware/ts-type-safety-utils"; import { pricesCache } from "@/lib/cache/pricesCache.js"; import { productsCache } from "@/lib/cache/productsCache.js"; -import { isValidStripeID } from "@/lib/stripe/isValidStripeID.js"; +import { isValidStripeID } from "@/lib/stripe/helpers.js"; import { userModelHelpers } from "@/models/User/helpers.js"; import { COMMON_ATTRIBUTE_TYPES, COMMON_ATTRIBUTES } from "@/models/_common/modelAttributes.js"; import { ddbTable } from "@/models/ddbTable.js"; -import { SUBSCRIPTION_ENUM_CONSTANTS } from "./enumConstants.js"; -import { userSubscriptionModelHelpers as subModelHelpers } from "./helpers.js"; -import { normalizeStripeFields } from "./normalizeStripeFields.js"; -import { USER_SUB_SK_PREFIX_STR as SUB_SK_PREFIX } from "./regex.js"; -import { upsertOne } from "./upsertOne.js"; -import { validateSubscription, validatePriceID, validatePromoCode } from "./validators.js"; -import type { OpenApiSchemas } from "@/types/open-api.js"; +import { + SUBSCRIPTION_ENUMS, + SUBSCRIPTION_PRICE_NAMES as PRICE_NAMES, + SUBSCRIPTION_PRODUCT_NAMES as PRODUCT_NAMES, +} from "./enumConstants.js"; +import { subModelHelpers, SUB_SK_PREFIX_STR } from "./helpers.js"; +import type { SubscriptionPriceName } from "@/types/graphql.js"; import type { ItemTypeFromSchema, ItemCreationParameters } from "@nerdware/ddb-single-table"; import type Stripe from "stripe"; @@ -20,11 +20,11 @@ import type Stripe from "stripe"; * UserSubscription Model */ class UserSubscriptionModel extends Model { - static readonly PRODUCT_ID = productsCache.get("Fixit Subscription")!.id; - static readonly PRICE_IDS: Record = { - ANNUAL: pricesCache.get("ANNUAL")!.id, - MONTHLY: pricesCache.get("MONTHLY")!.id, - TRIAL: pricesCache.get("TRIAL")!.id, + static readonly PRODUCT_ID = productsCache.get(PRODUCT_NAMES.FIXIT_SUBSCRIPTION)!.id; + static readonly PRICE_IDS: Record = { + ANNUAL: pricesCache.get(PRICE_NAMES.ANNUAL)!.id, + MONTHLY: pricesCache.get(PRICE_NAMES.MONTHLY)!.id, + TRIAL: pricesCache.get(PRICE_NAMES.TRIAL)!.id, }; static readonly schema = ddbTable.getModelSchema({ @@ -37,7 +37,7 @@ class UserSubscriptionModel extends Model { sk: { type: "string", default: (sub: { pk?: string; createdAt?: Date }) => - sub?.pk && sub?.createdAt ? subModelHelpers.sk.format(sub.pk, sub.createdAt) : undefined, + sub.pk && sub.createdAt ? subModelHelpers.sk.format(sub.pk, sub.createdAt) : undefined, validate: subModelHelpers.sk.isValid, required: true, }, @@ -58,14 +58,13 @@ class UserSubscriptionModel extends Model { // Not using type=enum here bc Product IDs are env-dependent and not known until runtime. transformValue: { // This toDB allows the value to be a Product `id` OR `name` - toDB: (value: string) => - productsCache.has(value as any) ? productsCache.get(value as any)!.id : value, + toDB: (value: string) => (productsCache.has(value) ? productsCache.get(value)!.id : value), }, }, priceID: { type: "string", required: true, - validate: validatePriceID, + validate: subModelHelpers.priceID.isValid, // Not using type=enum here bc Price IDs are env-dependent and not known until runtime. transformValue: { // This toDB allows the value to be a Price `id` OR `name` @@ -77,7 +76,7 @@ class UserSubscriptionModel extends Model { }, status: { type: "enum", - oneOf: SUBSCRIPTION_ENUM_CONSTANTS.STATUSES, + oneOf: SUBSCRIPTION_ENUMS.STATUSES, required: true, }, ...COMMON_ATTRIBUTES.TIMESTAMPS, // "createdAt" and "updatedAt" timestamps @@ -90,29 +89,22 @@ class UserSubscriptionModel extends Model { // USER SUBSCRIPTION MODEL — Instance properties and methods: readonly PRODUCT_ID = UserSubscriptionModel.PRODUCT_ID; readonly PRICE_IDS = UserSubscriptionModel.PRICE_IDS; - readonly SK_PREFIX = SUB_SK_PREFIX; + readonly SK_PREFIX = SUB_SK_PREFIX_STR; readonly getFormattedSK = subModelHelpers.sk.format; - readonly normalizeStripeFields = normalizeStripeFields; - readonly upsertOne = upsertOne; - readonly validateExisting = validateSubscription; - readonly validatePriceID = validatePriceID; - readonly validatePromoCode = validatePromoCode; } +/** UserSubscription Model */ export const UserSubscription = new UserSubscriptionModel(); /** The shape of a `UserSubscription` object returned from Model methods. */ export type UserSubscriptionItem = ItemTypeFromSchema; /** `UserSubscription` item params for `createItem()`. */ -export type UserSubscriptionItemCreationParams = ItemCreationParameters< +export type UserSubscriptionCreateItemParams = ItemCreationParameters< typeof UserSubscriptionModel.schema >; -/** - * The shape of a `UserSubscription` object in the DB. - * > This type is used to mock `@aws-sdk/lib-dynamodb` responses. - */ +/** The shape of a raw/unaliased `UserSubscription` object in the DB. */ export type UnaliasedUserSubscriptionItem = ItemTypeFromSchema< typeof UserSubscriptionModel.schema, { @@ -121,6 +113,3 @@ export type UnaliasedUserSubscriptionItem = ItemTypeFromSchema< nullableIfOptional: true; } >; - -/** The names of Fixit Subscription prices: "TRIAL", "MONTHLY", "ANNUAL" */ -export type SubscriptionPriceLabels = OpenApiSchemas["SubscriptionPriceName"]; diff --git a/src/models/UserSubscription/enumConstants.ts b/src/models/UserSubscription/enumConstants.ts index 55b36463..70e7ccbb 100644 --- a/src/models/UserSubscription/enumConstants.ts +++ b/src/models/UserSubscription/enumConstants.ts @@ -1,8 +1,10 @@ -import type { SubscriptionStatus } from "@/types/graphql.js"; +import type { SubscriptionStatus, SubscriptionPriceName } from "@/types/graphql.js"; -export const SUBSCRIPTION_ENUM_CONSTANTS: { - readonly STATUSES: ReadonlyArray; -} = { +/** + * A map of all subscription enums. + */ +export const SUBSCRIPTION_ENUMS = { + PRICE_NAMES: ["ANNUAL", "MONTHLY", "TRIAL"] as const, STATUSES: [ "active", "canceled", @@ -12,4 +14,28 @@ export const SUBSCRIPTION_ENUM_CONSTANTS: { "trialing", "unpaid", ] as const, +} as const satisfies { + readonly PRICE_NAMES: ReadonlyArray; + readonly STATUSES: ReadonlyArray; +}; + +/** + * A map of all subscription price names. + */ +export const SUBSCRIPTION_PRICE_NAMES = Object.fromEntries( + SUBSCRIPTION_ENUMS.PRICE_NAMES.map((name) => [name, name]) +) as { readonly [Name in SubscriptionPriceName]: Name }; + +/** + * A map of all subscription statuses. + */ +export const SUBSCRIPTION_STATUSES = Object.fromEntries( + SUBSCRIPTION_ENUMS.STATUSES.map((status) => [status, status]) +) as { readonly [Status in SubscriptionStatus]: Status }; + +/** + * A map of all subscription product names. + */ +export const SUBSCRIPTION_PRODUCT_NAMES = { + FIXIT_SUBSCRIPTION: "Fixit Subscription" as const, } as const; diff --git a/src/models/UserSubscription/helpers.ts b/src/models/UserSubscription/helpers.ts index 14fed136..9fb68a77 100644 --- a/src/models/UserSubscription/helpers.ts +++ b/src/models/UserSubscription/helpers.ts @@ -1,17 +1,31 @@ +import { isString } from "@nerdware/ts-type-safety-utils"; import dayjs from "dayjs"; -import { createModelHelpers } from "@/models/_common/modelHelpers.js"; -import { - USER_SUB_SK_PREFIX_STR as SUB_SK_PREFIX, - USER_SUB_SK_REGEX as SUB_SK_REGEX, -} from "./regex.js"; +import { pricesCache } from "@/lib/cache/pricesCache.js"; +import { userModelHelpers } from "@/models/User/helpers.js"; +import { createMapOfStringAttrHelpers, getCompoundAttrRegex, DELIMETER } from "@/models/_common"; +import { UNIX_TIMESTAMP_REGEX } from "@/utils/timestamps.js"; -export const userSubscriptionModelHelpers = createModelHelpers({ - sk: { - regex: SUB_SK_REGEX, +export const SUB_SK_PREFIX_STR = "SUBSCRIPTION"; - /** Returns a formatted UserSubscription "sk" value */ - format: (userID: string, createdAt: Date) => { - return `${SUB_SK_PREFIX}#${userID}#${dayjs(createdAt).unix()}`; +export const subModelHelpers = { + ...createMapOfStringAttrHelpers({ + sk: { + /** Validation regex for `UserSubscription.sk` values. */ + regex: getCompoundAttrRegex([ + SUB_SK_PREFIX_STR, + userModelHelpers.id.regex, + UNIX_TIMESTAMP_REGEX, + ]), + /** Returns a formatted UserSubscription "sk" value. */ + format: (userID: string, createdAt: Date) => { + return `${SUB_SK_PREFIX_STR}${DELIMETER}${userID}${DELIMETER}${dayjs(createdAt).unix()}`; + }, + }, + }), + /** priceID validation uses cache data rather than regex patterns. */ + priceID: { + isValid: (value?: unknown) => { + return isString(value) && pricesCache.values().some(({ id: priceID }) => priceID === value); }, }, -}); +}; diff --git a/src/models/UserSubscription/index.ts b/src/models/UserSubscription/index.ts index 880fc4f4..533811f8 100644 --- a/src/models/UserSubscription/index.ts +++ b/src/models/UserSubscription/index.ts @@ -1 +1,2 @@ export * from "./UserSubscription.js"; +export * from "./helpers.js"; diff --git a/src/models/UserSubscription/regex.ts b/src/models/UserSubscription/regex.ts deleted file mode 100644 index ce886c75..00000000 --- a/src/models/UserSubscription/regex.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { USER_ID_REGEX_STR } from "@/models/User/regex.js"; -import { UNIX_TIMESTAMP_REGEX_STR } from "@/utils/regex.js"; - -export const USER_SUB_SK_PREFIX_STR = "SUBSCRIPTION"; -export const USER_SUB_SK_REGEX_STR = `^${USER_SUB_SK_PREFIX_STR}#${USER_ID_REGEX_STR}#${UNIX_TIMESTAMP_REGEX_STR}$`; -export const USER_SUB_SK_REGEX = new RegExp(USER_SUB_SK_REGEX_STR); diff --git a/src/models/UserSubscription/upsertOne.ts b/src/models/UserSubscription/upsertOne.ts deleted file mode 100644 index b7fcb128..00000000 --- a/src/models/UserSubscription/upsertOne.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { promoCodesCache } from "@/lib/cache/promoCodesCache.js"; -import { stripe } from "@/lib/stripe/stripeClient.js"; -import { UserInputError } from "@/utils/httpErrors.js"; -import { - UserSubscription, - type UserSubscriptionItem, - type SubscriptionPriceLabels, -} from "./UserSubscription.js"; -import type { StripeSubscriptionWithClientSecret } from "@/lib/stripe/types.js"; -import type { UserItem } from "@/models/User/User.js"; -import type Stripe from "stripe"; - -/** - * `upsertOne` - * - * **priceID** can be explicitly provided, or it can be looked up if a valid name key is - * provided ("TRIAL", "MONTHLY", or "ANNUAL") to the **selectedSubscription** property. - */ -export const upsertOne = async function ( - this: typeof UserSubscription, - { - user: { id: userID, stripeCustomerID }, - selectedSubscription, - priceID, - promoCode, - }: { - user: Pick; - selectedSubscription?: SubscriptionPriceLabels; - priceID?: string | undefined; - promoCode?: string | undefined; - } -): Promise & UserSubscriptionItem> { - // Ascertain the subscription's Stripe price ID - if (!priceID && !!selectedSubscription) { - priceID = UserSubscription.PRICE_IDS[selectedSubscription]; - } - - if (!priceID) throw new UserInputError("Invalid subscription"); - - // Ascertain the subscription's Stripe promoCode ID if applicable - const promoCodeID = promoCodesCache.get(promoCode ?? "")?.id; - - // Submit info to Stripe API for new subscription - const stripeSubObject = (await stripe.subscriptions.create({ - customer: stripeCustomerID, - items: [{ price: priceID }], - ...(promoCodeID && { promotion_code: promoCodeID }), - ...(selectedSubscription === "TRIAL" && { trial_period_days: 14 }), - payment_behavior: "default_incomplete", - payment_settings: { save_default_payment_method: "on_subscription" }, - expand: ["latest_invoice.payment_intent", "customer"], - })) as Stripe.Response; - - // Get the fields needed from the returned object - const { createdAt, currentPeriodEnd, productID } = - UserSubscription.normalizeStripeFields(stripeSubObject); - - const userSubscription = { - userID, - sk: UserSubscription.getFormattedSK(userID, createdAt), - id: stripeSubObject.id, - currentPeriodEnd, - productID, - priceID, - status: stripeSubObject.status, - createdAt, - }; - - // Upsert the sub info to ensure db is up to date and prevent duplicate user subs. - const { updatedAt } = await UserSubscription.upsertItem(userSubscription); - - // Return both the API UserSub object and the Stripe response object - return { - ...stripeSubObject, - ...userSubscription, - updatedAt, - }; -}; diff --git a/src/models/UserSubscription/validators.ts b/src/models/UserSubscription/validators.ts deleted file mode 100644 index 3f2ca019..00000000 --- a/src/models/UserSubscription/validators.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { hasKey, isString } from "@nerdware/ts-type-safety-utils"; -import dayjs from "dayjs"; -import { pricesCache } from "@/lib/cache/pricesCache.js"; -import { promoCodesCache } from "@/lib/cache/promoCodesCache.js"; -import type { SubscriptionStatus } from "@/types/graphql.js"; -import type { UserSubscriptionItem } from "./UserSubscription.js"; - -/** - * This function is used to validate an existing UserSubscription. A valid - * subscription is required to access the Fixit API and related services. - * - * A UserSubscription is only considered valid if all of the following are true: - * - * 1. The subscription's status is `active` or `trialing`. - * 2. The subscription's `currentPeriodEnd` timestamp is in the future. - */ -export const validateSubscription = function ({ - status, - currentPeriodEnd, -}: Partial = {}) { - if ( - !status || - !hasKey(SUBSCRIPTION_STATUS_METADATA, status) || - SUBSCRIPTION_STATUS_METADATA[status]?.isValid !== true || - !currentPeriodEnd || - !dayjs(currentPeriodEnd).isValid() - ) { - throw new Error( - !!status && hasKey(SUBSCRIPTION_STATUS_METADATA, status) - ? SUBSCRIPTION_STATUS_METADATA[status]?.reason ?? "Invalid subscription." - : "Invalid subscription." - ); - } - - // Coerce to unix timestamp in seconds and compare - if (dayjs().unix() >= dayjs(currentPeriodEnd).unix()) { - throw new Error( - "This subscription has expired - please submit payment to re-activate your subscription." - ); - } -}; - -/** - * Subscription-Status metadata objects: - * - Subscription statuses which indicate a sub IS VALID for Fixit service usage - * contain `isValid: true`. - * - Subscription statuses which indicate a sub IS NOT VALID for Fixit service - * usage contain a `reason` plainly explaining to end users _why_ their sub is - * invalid, and what their course of action should be to resolve the issue. - * - * @see https://stripe.com/docs/api/subscriptions/object#subscription_object-status - */ -export const SUBSCRIPTION_STATUS_METADATA: Readonly< - Record< - SubscriptionStatus, - { isValid: true; reason?: never } | { isValid?: false; reason: string } - > -> = { - // Statuses which indicate a subscription IS VALID for service usage: - active: { - isValid: true, - }, - trialing: { - isValid: true, - }, - // Statuses which indicate a subscription IS NOT VALID for service usage: - incomplete: { - reason: "Sorry, your subscription payment is incomplete.", - }, - incomplete_expired: { - reason: "Sorry, please try again.", - }, - past_due: { - reason: "Sorry, payment for your subscription is past due. Please submit payment and try again.", // prettier-ignore - }, - canceled: { - reason: "Sorry, this subscription was canceled.", - }, - unpaid: { - reason: "Sorry, payment for your subscription is past due. Please submit payment and try again.", // prettier-ignore - }, -}; - -/** - * Validates a price ID string - throws an error if the provided string is not a valid price ID. - * - If the provided string is a valid priceID, returns `true`. - * - If the provided string is not a valid priceID: - * - If `shouldThrowIfInvalid` is `false` (default), returns `false`. - * - If `shouldThrowIfInvalid` is `true`, throws an error. - */ -export const validatePriceID = (maybePriceID?: unknown, shouldThrowIfInvalid: boolean = false) => { - const isValid = - isString(maybePriceID) && - pricesCache.values().some(({ id: priceID }) => maybePriceID === priceID); - - if (!isValid && shouldThrowIfInvalid) throw new Error("Invalid subscription"); - - return isValid; -}; - -/** - * Validates a promo code string - throws an error if the provided string is not a valid promo code. - * - If the provided string is a valid promoCode, returns `true`. - * - If the provided string is not a valid promoCode: - * - If `shouldThrowIfInvalid` is `false` (default), returns `false`. - * - If `shouldThrowIfInvalid` is `true`, throws an error. - */ -export const validatePromoCode = ( - maybePromoCode?: unknown, - shouldThrowIfInvalid: boolean = false -) => { - const isValid = isString(maybePromoCode) && promoCodesCache.has(maybePromoCode); - - if (!isValid && shouldThrowIfInvalid) throw new Error("Invalid promo code"); - - return isValid; -}; diff --git a/src/models/WorkOrder/WorkOrder.test.ts b/src/models/WorkOrder/WorkOrder.test.ts index 2dd3bdde..c0bcc5e9 100644 --- a/src/models/WorkOrder/WorkOrder.test.ts +++ b/src/models/WorkOrder/WorkOrder.test.ts @@ -1,4 +1,4 @@ -import { Location } from "@/models/Location/Location.js"; +import { Location } from "@/models/Location"; import { MOCK_WORK_ORDERS, UNALIASED_MOCK_WORK_ORDERS, @@ -10,7 +10,7 @@ const { WO_A, WO_B, WO_C } = MOCK_WORK_ORDERS; describe("WorkOrder Model", () => { describe("WorkOrder.createItem()", () => { - test("returns a valid WorkOrder when called with valid arguments", async () => { + test("returns a valid WorkOrder when called with valid arguments (ORIGINAL", async () => { // Arrange mock WorkOrders for (const key in MOCK_WORK_ORDERS) { // Get createItem inputs from mock WorkOrder @@ -27,6 +27,7 @@ describe("WorkOrder Model", () => { }); } }); + test(`throws an Error when called without a valid "createdByUserID"`, async () => { await expect(() => WorkOrder.createItem({ diff --git a/src/models/WorkOrder/WorkOrder.ts b/src/models/WorkOrder/WorkOrder.ts index 6dd25350..9833903e 100644 --- a/src/models/WorkOrder/WorkOrder.ts +++ b/src/models/WorkOrder/WorkOrder.ts @@ -1,16 +1,14 @@ import { Model } from "@nerdware/ddb-single-table"; import { isString } from "@nerdware/ts-type-safety-utils"; -import { Location } from "@/models/Location/Location.js"; +import { Location } from "@/models/Location"; import { userModelHelpers } from "@/models/User/helpers.js"; import { COMMON_ATTRIBUTE_TYPES, COMMON_ATTRIBUTES } from "@/models/_common/modelAttributes.js"; import { ddbTable } from "@/models/ddbTable.js"; import { WORK_ORDER_ENUM_CONSTANTS } from "./enumConstants.js"; -import { workOrderModelHelpers as woModelHelpers } from "./helpers.js"; -import { WORK_ORDER_SK_PREFIX_STR } from "./regex.js"; +import { workOrderModelHelpers as woModelHelpers, WO_SK_PREFIX_STR } from "./helpers.js"; import type { ItemTypeFromSchema, ItemCreationParameters, - ItemParameters, ModelSchemaOptions, } from "@nerdware/ddb-single-table"; import type { OverrideProperties } from "type-fest"; @@ -33,10 +31,8 @@ class WorkOrderModel extends Model< sk: { type: "string", alias: "id", - default: (woItem: { pk?: string; createdAt?: Date }) => - woItem?.pk && woItem?.createdAt - ? woModelHelpers.id.format(woItem.pk, woItem.createdAt) - : undefined, + default: ({ pk: createdByUserID }: { pk?: string }) => + createdByUserID ? woModelHelpers.id.format(createdByUserID) : undefined, validate: woModelHelpers.id.isValid, required: true, }, @@ -69,13 +65,7 @@ class WorkOrderModel extends Model< validate: (value: string) => Location.validateCompoundString(value), required: true, transformValue: { - /* Clients provide "location" as an object with properties "country", "region", "city", - "streetLine1", and "streetLine2". This transformation converts these client location input - objects into a string which adheres to the above pattern and serves as a composite attribute - value. Storing "location" in this way makes it possible to flexibly query the DynamoDB db - for access patterns like "Find all work orders on Foo Street". */ toDB: (location: Location) => Location.convertToCompoundString(location), - // This fromDB reverses the toDB, returning a "location" object from db format. fromDB: (locationCompoundStr: string) => Location.parseCompoundString(locationCompoundStr), }, }, @@ -97,7 +87,7 @@ class WorkOrderModel extends Model< schema: { id: { type: "string", - default: (woItem: { sk: string }) => woModelHelpers.checklistItemID.format(woItem.sk), + default: (wo: { sk: string }) => woModelHelpers.checklistItemID.format(wo.sk), validate: woModelHelpers.checklistItemID.isValid, required: true, }, @@ -146,9 +136,10 @@ class WorkOrderModel extends Model< readonly PRIORITIES = WORK_ORDER_ENUM_CONSTANTS.PRIORITIES; readonly STATUSES = WORK_ORDER_ENUM_CONSTANTS.STATUSES; readonly CATEGORIES = WORK_ORDER_ENUM_CONSTANTS.CATEGORIES; - readonly SK_PREFIX = WORK_ORDER_SK_PREFIX_STR; + readonly SK_PREFIX = WO_SK_PREFIX_STR; } +/** WorkOrder Model */ export const WorkOrder = new WorkOrderModel(); /** The shape of a `WorkOrder` object returned from WorkOrderModel methods. */ @@ -163,13 +154,7 @@ export type WorkOrderItemCreationParams = OverrideProperties< { assignedToUserID: string | null; location: Location } >; -/** `WorkOrder` item params for `updateItem()`. */ -export type WorkOrderItemUpdateParams = ItemParameters; - -/** - * The shape of a `WorkOrder` object in the DB. - * > This type is used to mock `@aws-sdk/lib-dynamodb` responses. - */ +/** The shape of a raw/unaliased `WorkOrder` object in the DB. */ export type UnaliasedWorkOrderItem = ItemTypeFromSchema< typeof WorkOrderModel.schema, { diff --git a/src/models/WorkOrder/enumConstants.ts b/src/models/WorkOrder/enumConstants.ts index 424d10ae..58a6a83d 100644 --- a/src/models/WorkOrder/enumConstants.ts +++ b/src/models/WorkOrder/enumConstants.ts @@ -1,16 +1,14 @@ import type { WorkOrderStatus, WorkOrderCategory, WorkOrderPriority } from "@/types/graphql.js"; -export const WORK_ORDER_ENUM_CONSTANTS: { - readonly PRIORITIES: ReadonlyArray; - readonly STATUSES: ReadonlyArray; - readonly CATEGORIES: ReadonlyArray; -} = { +/** + * A map of all work order enums. + */ +export const WORK_ORDER_ENUM_CONSTANTS = { PRIORITIES: [ "LOW", "NORMAL", // <-- default "HIGH", ] as const, - STATUSES: [ "UNASSIGNED", // <-- WO has not been assigned to anyone "ASSIGNED", // <-- WO has merely been assigned to someone @@ -19,7 +17,6 @@ export const WORK_ORDER_ENUM_CONSTANTS: { "CANCELLED", // <-- Assignor can not delete ASSIGNED/COMPLETE WOs, only mark them as "CANCELLED" (deleted from db after 90 days if not updated nor attached to an Invoice) "COMPLETE", // <-- Assignee notifies Assignor that WO is "COMPLETE" (may be reverted to "ASSIGNED" by either party) ] as const, - CATEGORIES: [ "DRYWALL", "ELECTRICAL", @@ -37,4 +34,22 @@ export const WORK_ORDER_ENUM_CONSTANTS: { "TURNOVER", "WINDOWS", ] as const, -} as const; +} as const satisfies { + readonly PRIORITIES: ReadonlyArray; + readonly STATUSES: ReadonlyArray; + readonly CATEGORIES: ReadonlyArray; +}; + +/** + * A map of all work order priority values. + */ +export const WORK_ORDER_PRIORITIES = Object.fromEntries( + WORK_ORDER_ENUM_CONSTANTS.PRIORITIES.map((priority) => [priority, priority]) +) as { readonly [Priority in WorkOrderPriority]: Priority }; + +/** + * A map of all work order statuses. + */ +export const WORK_ORDER_STATUSES = Object.fromEntries( + WORK_ORDER_ENUM_CONSTANTS.STATUSES.map((status) => [status, status]) +) as { readonly [Status in WorkOrderStatus]: Status }; diff --git a/src/models/WorkOrder/helpers.ts b/src/models/WorkOrder/helpers.ts index bde357ec..993d4ea0 100644 --- a/src/models/WorkOrder/helpers.ts +++ b/src/models/WorkOrder/helpers.ts @@ -1,48 +1,33 @@ -import { isDate, isString } from "@nerdware/ts-type-safety-utils"; -import { createModelHelpers } from "@/models/_common/modelHelpers.js"; -import { getUnixTimestampUUID } from "@/utils/uuid.js"; -import { - WORK_ORDER_SK_PREFIX_STR as WO_SK_PREFIX, - WORK_ORDER_ID_REGEX, - WO_CHECKLIST_ITEM_ID_REGEX, -} from "./regex.js"; +import { userModelHelpers } from "@/models/User/helpers.js"; +import { createMapOfStringAttrHelpers, getCompoundAttrRegex, DELIMETER } from "@/models/_common"; +import { getRandomUUIDv4, UUID_REGEX } from "@/utils/uuid.js"; -export const workOrderModelHelpers = createModelHelpers({ - id: { - regex: WORK_ORDER_ID_REGEX, - - /** Returns a formatted WorkOrder "id" value (alias for "sk" attribute) */ +export const WO_SK_PREFIX_STR = "WO"; +export const WO_CHECKLIST_ITEM_ID_INFIX_STR = "CHECKLIST_ITEM"; - /** - * WorkOrder "id" value formatter (alias for "sk" attribute). - * - * @param {Date|string} createdAt - The WorkOrder's "createdAt" timestamp value represented as - * either a Date object or unix timestamp UUID string. If provided as a Date object, it will - * be converted to a Unix timestamp UUID string. - * - * @returns {string} A formatted WorkOrder "id" value (alias for "sk" attribute). - */ - format: (createdByUserID: string, createdAt: Date | string) => { - // prettier-ignore - return `${WO_SK_PREFIX}#${createdByUserID}#${isDate(createdAt) ? getUnixTimestampUUID(createdAt) : createdAt}`; +export const workOrderModelHelpers = createMapOfStringAttrHelpers({ + id: { + regex: getCompoundAttrRegex([WO_SK_PREFIX_STR, userModelHelpers.id.regex, UUID_REGEX]), + /** WorkOrder "id" value formatter. */ + format: (createdByUserID: string) => { + return `${WO_SK_PREFIX_STR}${DELIMETER}${createdByUserID}${DELIMETER}${getRandomUUIDv4()}`; }, + /** Sanitizes a WorkOrder ID value. */ + sanitize: (str: string) => str.replace(/[^a-zA-Z0-9_@#-]/g, ""), // handle chars, UUID chars, and the delimeter }, checklistItemID: { - regex: WO_CHECKLIST_ITEM_ID_REGEX, - - /** - * WorkOrder checklist item "id" value formatter. - * - * @param {Date|string} createdAt - The WorkOrder checklist item's "createdAt" timestamp value - * represented as either a Date object or unix timestamp UUID string. If not provided, the - * current time will be used (`new Date()`). If provided as a Date object, it will be converted - * to a Unix timestamp UUID string. - * - * @returns {string} A formatted WorkOrder checklist item "id" value. - */ - format: (woID: string, createdAt?: Date | string) => { - // prettier-ignore - return `${woID}#CHECKLIST_ITEM#${isString(createdAt) ? createdAt : getUnixTimestampUUID(createdAt)}`; + regex: getCompoundAttrRegex([ + // The WorkOrder: + WO_SK_PREFIX_STR, + userModelHelpers.id.regex, + UUID_REGEX, + // The checklist item: + WO_CHECKLIST_ITEM_ID_INFIX_STR, + UUID_REGEX, + ]), + /** WorkOrder checklist item "id" value formatter. */ + format: (woID: string) => { + return `${woID}${DELIMETER}${WO_CHECKLIST_ITEM_ID_INFIX_STR}${DELIMETER}${getRandomUUIDv4()}`; }, }, }); diff --git a/src/models/WorkOrder/index.ts b/src/models/WorkOrder/index.ts index 6b6587af..46822b44 100644 --- a/src/models/WorkOrder/index.ts +++ b/src/models/WorkOrder/index.ts @@ -1 +1,2 @@ export * from "./WorkOrder.js"; +export * from "./helpers.js"; diff --git a/src/models/WorkOrder/regex.ts b/src/models/WorkOrder/regex.ts deleted file mode 100644 index 09cca6d4..00000000 --- a/src/models/WorkOrder/regex.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { USER_ID_REGEX_STR } from "@/models/User/regex.js"; -import { UUID_V1_REGEX_STR } from "@/utils/regex.js"; - -export const WORK_ORDER_SK_PREFIX_STR = "WO"; -export const WORK_ORDER_ID_REGEX_STR = `${WORK_ORDER_SK_PREFIX_STR}#${USER_ID_REGEX_STR}#${UUID_V1_REGEX_STR}`; -export const WORK_ORDER_ID_REGEX = new RegExp(`^${WORK_ORDER_ID_REGEX_STR}$`); - -export const WO_CHECKLIST_ITEM_ID_REGEX_STR = `^${WORK_ORDER_ID_REGEX_STR}#CHECKLIST_ITEM#${UUID_V1_REGEX_STR}$`; -export const WO_CHECKLIST_ITEM_ID_REGEX = new RegExp(WO_CHECKLIST_ITEM_ID_REGEX_STR); diff --git a/src/models/__mocks__/ddbTable.ts b/src/models/__mocks__/ddbTable.ts index bd21c29a..acee5ff4 100644 --- a/src/models/__mocks__/ddbTable.ts +++ b/src/models/__mocks__/ddbTable.ts @@ -16,7 +16,7 @@ export const ddbTable = new Table({ required: true, isRangeKey: true, index: { - name: "Overloaded_SK_GSI", // For relational queryies using "sk" as the hash key + name: "Overloaded_SK_GSI", // For queries using "sk" as the hash key rangeKey: "data", global: true, project: true, // all attributes @@ -27,8 +27,8 @@ export const ddbTable = new Table({ type: "string", required: true, index: { - name: "Overloaded_Data_GSI", // For relational queries using "data" as the hash key - rangeKey: "sk", // WO query "WorkOrdersAssignedToUser" uses this GSI SK + name: "Overloaded_Data_GSI", // For queries using "data" as the hash key + rangeKey: "sk", global: true, project: true, // all attributes throughput: { read: 5, write: 5 }, diff --git a/src/models/_common/attributeHelpers.ts b/src/models/_common/attributeHelpers.ts new file mode 100644 index 00000000..1ed20446 --- /dev/null +++ b/src/models/_common/attributeHelpers.ts @@ -0,0 +1,89 @@ +import { isString } from "@nerdware/ts-type-safety-utils"; +import { UserInputError } from "@/utils/httpErrors.js"; + +/** + * This function serves as a factory for creating {@link MapOfStringAttrHelpers} objects, which + * map model attribute names to objects with `format` and `isValid` methods for formatting and + * validating attribute values. + * + * @param attrInputs - A map of attribute names to {@link BaseStringAttrHelpersInput} objects. + * @returns A {@link MapOfStringAttrHelpers} object. + */ +export const createMapOfStringAttrHelpers = ( + attrInputs: T +) => { + const attrHelpers: Partial> = {}; + + for (const attrName in attrInputs) { + attrHelpers[attrName] = createHelpersForStrAttr(attrName, attrInputs[attrName]!); + } + + return attrHelpers as MapOfStringAttrHelpers; +}; + +/** + * Creates a single attribute helper object for a string attribute. + */ +export const createHelpersForStrAttr = ( + attrName: string, + { regex, sanitize, ...configs }: T +) => { + const isValid = (value?: unknown): value is string => isString(value) && regex.test(value); + + const validate = (value?: unknown) => { + if (!isValid(value)) throw new UserInputError(`Invalid "${attrName}" value`); + }; + + return { + ...configs, + regex, + isValid, + validate, + ...(!!sanitize && { + sanitize, + sanitizeAndValidate: (value: string) => { + const sanitizedValue = sanitize(value); + validate(sanitizedValue); + return sanitizedValue; + }, + }), + } as unknown as StringAttrHelpers; +}; + +/////////////////////////////////////////////////////////////////////////////// +// INPUT TYPES: + +type BaseMapOfStringAttrHelpersInput = Record; + +type BaseStringAttrHelpersInput = { + /** Validation regex used to create `isValid` and `validate` attribute helper methods. */ + regex: RegExp; + /** + * Sanitizes a value for the respective attribute by removing any invalid characters. + * If provided, this method is used in the `validate` method before checking the value's validity. + */ + sanitize?: (value: string) => string; + /** Returns a string formatted for the respective attribute using the provided values. */ + format?: (...args: any[]) => string; + [otherProperties: string]: unknown; +}; + +/////////////////////////////////////////////////////////////////////////////// +// OUTPUT TYPES: + +/** Map of attrName keys to methods for validating and formatting string attr values. */ +export type MapOfStringAttrHelpers< + T extends BaseMapOfStringAttrHelpersInput = BaseMapOfStringAttrHelpersInput, +> = { [AttrName in keyof T]: StringAttrHelpers }; + +/** An object with methods for formatting and validating a string-attribute's values. */ +type StringAttrHelpers = T & { + /** Returns a boolean indicating whether the provided value is valid. */ + isValid: (value?: unknown) => boolean; + /** Validates the provided value, and throws a {@link UserInputError} if invalid. */ + validate: (value?: unknown) => void; + /** Sanitizes and validates the provided value, and throws a {@link UserInputError} if invalid. */ + sanitizeAndValidate: T["sanitize"] extends (value: string) => string + ? (value: string) => string + : never; +}; diff --git a/src/models/_common/compoundAttributeHelpers.ts b/src/models/_common/compoundAttributeHelpers.ts new file mode 100644 index 00000000..117ee08f --- /dev/null +++ b/src/models/_common/compoundAttributeHelpers.ts @@ -0,0 +1,116 @@ +import { isPlainObject, isString } from "@nerdware/ts-type-safety-utils"; + +/** Compound attribute field delimeter. */ +export const DELIMETER = "#"; + +/** + * Formats an individual {@link CompoundAttrRegexComponent|component} of a compound attribute's + * regex pattern. The following modifications are made to the provided regex pattern: + * + * - Removes any `^` and `$` position assertions + * - If the provided `regexComponent` is a config object with `required: false`, the regex + * pattern is grouped with a `?` quantifier. + */ +const formatCompoundAttrRegexComponent = (regexComponent: CompoundAttrRegexComponent): string => { + const { regex, required } = isPlainObject(regexComponent) + ? regexComponent + : { regex: regexComponent, required: true }; + + // Get the regex pattern source + let source = isString(regex) ? regex : regex.source; + + // Remove any `^` and `$` position assertions + source = source.replace(/(^\^?|\$?$)/g, ""); + // If the component is optional, group it with a `?` quantifier + if (!required) source = `(${source})?`; + + return source; +}; + +/** + * A component of a compound attribute's regex pattern. Can be a `RegExp` object, a regex pattern + * string, or a config object that includes a regex pattern and a `required` flag. + */ +export type CompoundAttrRegexComponent = RegExp | string | CompoundAttrRegexComponentConfig; + +/** A config object for a compound attribute's regex component. */ +export type CompoundAttrRegexComponentConfig = { + regex: RegExp | string; + required: boolean; +}; + +/** + * Returns a `RegExp` object that can be used to validate a compound attribute value. + * + * > _This app uses `"#"` as the delimeter for compound attribute field values._ + * + * @param regexComponents - An array of `RegExp` objects, regex-pattern strings, and/or + * regex-pattern config objects which include a regex pattern and a `required` flag. + * These patterns are used to validate the individual components of the compound attribute. + * They must be provided in the order they appear in the compound attribute. + * > **Note:** + * > - If a provided `RegExp` object has flags, they are ignored. + * > - Any `^` and/or `$` position assertions are removed. + * > - To have a component be optional, provide a config object with a `required: false` flag. + * + * @param regexFlags - Optional flags to pass to the `RegExp` constructor. + * @returns A `RegExp` object that reflects a combination of the provided `componentRegex` patterns. + * @example + * ```ts + * getCompoundAttrRegex( // This example would return /^foo#bar#(baz)?$/i + * [ + * '^foo$', // <-- `^` and `$` position assertions are removed + * /bar/u, // <-- flags are ignored + * { + * regex: 'baz', // <-- can be RegExp or string + * required: false // <-- optional regex component + * } + * ], + * 'i' + * ); // => /^foo#bar#(baz)?$/i + * ``` + */ +export const getCompoundAttrRegex = ( + regexComponents: Array, + { regexFlags = "" }: { regexFlags?: string } = {} +): RegExp => { + const regexSourceStrings = regexComponents.map(formatCompoundAttrRegexComponent); + return new RegExp(`^${regexSourceStrings.join(DELIMETER)}$`, regexFlags); +}; + +/** + * Converts an ordered array of values into a compound attribute string. + * + * > If a value is `null` or `undefined`, it is treated as an empty string. + * > + * > If the values may include problematic characters like spaces or `#` characters (the delimeter), + * > set `shouldUrlEncode` to `true` to encode the values using `encodeURIComponent`. + */ +export const getCompoundAttrString = ( + orderedValues: Array, + { shouldUrlEncode = false }: { shouldUrlEncode?: boolean } = {} +) => { + return orderedValues + .map( + // prettier-ignore + shouldUrlEncode + ? (value) => (value ? encodeURIComponent(value) : "") + : (value) => value ?? "" + ) + .join(DELIMETER); +}; + +/** + * Converts a compound attribute string into an ordered array of string values. + * + * > Set `shouldUrlDecode` to `true` to decode the values using `decodeURIComponent`. + */ +export const parseCompoundAttrString = ( + compoundAttrStr: string, + { shouldUrlDecode = false }: { shouldUrlDecode?: boolean } = {} +) => { + const orderedValues = compoundAttrStr.split(DELIMETER); + return shouldUrlDecode + ? orderedValues.map((value) => (value ? decodeURIComponent(value) : "")) + : orderedValues; +}; diff --git a/src/models/_common/index.ts b/src/models/_common/index.ts index 26a0dbf8..7031cfc7 100644 --- a/src/models/_common/index.ts +++ b/src/models/_common/index.ts @@ -1,3 +1,4 @@ +export * from "./attributeHelpers.js"; +export * from "./compoundAttributeHelpers.js"; export * from "./modelAttributes.js"; -export * from "./modelHelpers.js"; -export * from "./skTypeGuards.js"; +// DO NOT EXPORT skTypeGuards FROM HERE, DOING SO CREATES CIRCULAR DEPENDENCY ISSUES. diff --git a/src/models/_common/modelAttributes.ts b/src/models/_common/modelAttributes.ts index effcd6bd..84d60acf 100644 --- a/src/models/_common/modelAttributes.ts +++ b/src/models/_common/modelAttributes.ts @@ -1,7 +1,7 @@ -import { isValidPhone } from "@nerdware/ts-string-helpers"; -import { isString } from "@nerdware/ts-type-safety-utils"; -import { fmt } from "@/utils/formatters"; -import { normalize } from "@/utils/normalize.js"; +import { isValidPhone, sanitizePhone } from "@nerdware/ts-string-helpers"; +import { isString, isSafeInteger } from "@nerdware/ts-type-safety-utils"; +import dayjs from "dayjs"; +import { prettifyPhoneNumStr } from "@/utils/formatters/phone.js"; import { isValidTimestamp } from "@/utils/timestamps.js"; import type { ModelSchemaAttributeConfig } from "@nerdware/ddb-single-table"; @@ -11,9 +11,9 @@ export const COMMON_ATTRIBUTE_TYPES = { validate: (value: unknown) => isString(value) && isValidPhone(value), transformValue: { /** If a phone-value is provided, all non-digit chars are rm'd */ - toDB: (value: unknown) => (isString(value) ? normalize.phone(value) : null), + toDB: (value: unknown) => (isString(value) ? sanitizePhone(value) : null), /** Prettify phone num strings like `"8881234567"` into `"(888) 123-4567"` */ - fromDB: (value: unknown) => (isString(value) ? fmt.prettifyPhoneNum(value) : null), + fromDB: (value: unknown) => (isString(value) ? prettifyPhoneNumStr(value) : null), }, }, @@ -24,6 +24,7 @@ export const COMMON_ATTRIBUTE_TYPES = { } as const satisfies Record>; export const COMMON_ATTRIBUTES = { + /** `"createdAt"` and `"updatedAt"` timestamps */ TIMESTAMPS: { createdAt: { ...COMMON_ATTRIBUTE_TYPES.DATETIME, @@ -39,5 +40,19 @@ export const COMMON_ATTRIBUTES = { toDB: () => new Date(), }, }, - } satisfies Record, -} as const; + }, + + /** The DDB table's TTL attribute */ + TTL: { + expiresAt: { + // AWS requires TTL attributes to be Unix timestamps in non-leap seconds + type: "number", + // Unix timestamps in seconds will be 10 digits long until Nov 20 2286 + validate: (value: unknown) => + isSafeInteger(value) && `${value}`.length === 10 && dayjs(value).isValid(), + transformValue: { + toDB: (value: Date | number) => dayjs(value).unix(), + }, + }, + }, +} as const satisfies Record>; diff --git a/src/models/_common/modelHelpers.ts b/src/models/_common/modelHelpers.ts deleted file mode 100644 index d0644f27..00000000 --- a/src/models/_common/modelHelpers.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { isString } from "@nerdware/ts-type-safety-utils"; - -/** - * This function serves as a factory for creating {@link ModelHelpers} objects, which consist - * of model attribute names as keys, each of which is given `format` and `isValid` methods for - * formatting and validating attribute values respectively. - * - * @param modelHelperConfigs - A record of attribute names to {@link ModelHelperConfigInput} objects. - * @returns A {@link ModelHelpers} object. - */ -export const createModelHelpers = ( - modelHelpersInput: AttrInputs -) => { - const modelHelpers: Record = {}; - - for (const attrName in modelHelpersInput) { - const { regex, ...configs } = modelHelpersInput[attrName]!; - - modelHelpers[attrName] = { - ...configs, - isValid: (value?: unknown) => isString(value) && regex.test(value), - }; - } - - return modelHelpers as ModelHelpers; -}; - -/** - * Record of attrName keys to methods for validating and formatting attr values. - */ -export type ModelHelpers = { - [Key in keyof AttrConfigs]: ModelHelpersAttributeConfig; -}; - -type ModelHelpersAttributeConfig< - T extends ModelHelpersAttributeConfigInput = ModelHelpersAttributeConfigInput, -> = Omit & { - /** Validates the provided value. */ - isValid: (value?: unknown) => boolean; -}; - -/** Parameters for {@link createModelHelpers} */ -type ModelHelpersInput = Record; - -type ModelHelpersAttributeConfigInput = { - /** Returns a string formatted for the respective attribute using the provided values. */ - format: (...args: any[]) => string; - regex: RegExp; - [otherProperties: string]: any; -}; diff --git a/src/models/_common/skTypeGuards.ts b/src/models/_common/skTypeGuards.ts index d71ba7f7..457b1b50 100644 --- a/src/models/_common/skTypeGuards.ts +++ b/src/models/_common/skTypeGuards.ts @@ -1,11 +1,13 @@ -import { CONTACT_SK_PREFIX_STR } from "@/models/Contact/regex.js"; -import { INVOICE_SK_PREFIX_STR } from "@/models/Invoice/regex.js"; -import { USER_SK_PREFIX_STR } from "@/models/User/regex.js"; -import { STRIPE_CONNECT_ACCOUNT_SK_PREFIX_STR as SCA_SK_PREFIX_STR } from "@/models/UserStripeConnectAccount/regex.js"; -import { USER_SUB_SK_PREFIX_STR as SUB_SK_PREFIX_STR } from "@/models/UserSubscription/regex.js"; -import { WORK_ORDER_SK_PREFIX_STR } from "@/models/WorkOrder/regex.js"; +import { CONTACT_SK_PREFIX_STR } from "@/models/Contact/helpers.js"; +import { INVOICE_SK_PREFIX_STR } from "@/models/Invoice/helpers.js"; +import { PW_RESET_TOKEN_SK_PREFIX_STR } from "@/models/PasswordResetToken/helpers.js"; +import { USER_SK_PREFIX_STR } from "@/models/User/helpers.js"; +import { SCA_SK_PREFIX_STR } from "@/models/UserStripeConnectAccount/helpers.js"; +import { SUB_SK_PREFIX_STR } from "@/models/UserSubscription/helpers.js"; +import { WO_SK_PREFIX_STR } from "@/models/WorkOrder/helpers.js"; import type { ContactItem } from "@/models/Contact/Contact.js"; import type { InvoiceItem } from "@/models/Invoice/Invoice.js"; +import type { PasswordResetTokenItem as PwResetTokenItem } from "@/models/PasswordResetToken/PasswordResetToken.js"; import type { UserItem } from "@/models/User/User.js"; import type { UserStripeConnectAccountItem as UserSCAModelItem } from "@/models/UserStripeConnectAccount/UserStripeConnectAccount.js"; import type { UserSubscriptionItem as UserSubModelItem } from "@/models/UserSubscription/UserSubscription.js"; @@ -15,13 +17,14 @@ import type { WorkOrderItem } from "@/models/WorkOrder/WorkOrder.js"; * Functions which ascertain whether a given string is a valid `sk` value for an * internal-db type. */ -export const isSKofType = { +const isSKofType = { contact: (str?: string) => !!str && str.startsWith(`${CONTACT_SK_PREFIX_STR}#`), invoice: (str?: string) => !!str && str.startsWith(`${INVOICE_SK_PREFIX_STR}#`), user: (str?: string) => !!str && str.startsWith(`${USER_SK_PREFIX_STR}#`), - stripeConnectAccount: (str?: string) => !!str && str.startsWith(`${SCA_SK_PREFIX_STR}#`), + sca: (str?: string) => !!str && str.startsWith(`${SCA_SK_PREFIX_STR}#`), subscription: (str?: string) => !!str && str.startsWith(`${SUB_SK_PREFIX_STR}#`), - workOrder: (str?: string) => !!str && str.startsWith(`${WORK_ORDER_SK_PREFIX_STR}#`), + workOrder: (str?: string) => !!str && str.startsWith(`${WO_SK_PREFIX_STR}#`), + pwResetToken: (str?: string) => !!str && str.startsWith(`${PW_RESET_TOKEN_SK_PREFIX_STR}#`), }; /** @@ -29,18 +32,18 @@ export const isSKofType = { * internal-database type using the object's `sk` attribute value. For example, * `skTypeGuards.isUser` determines if an object is a `UserItem`. */ +// prettier-ignore export const skTypeGuards = { - isContact: (obj: SkTypeGuardArg): obj is ContactItem => isSKofType.contact(obj?.sk), - isInvoice: (obj: SkTypeGuardArg): obj is InvoiceItem => isSKofType.invoice(obj?.sk), - isUser: (obj: SkTypeGuardArg): obj is UserItem => isSKofType.user(obj?.sk), - isUserStripeConnectAccount: (obj: SkTypeGuardArg): obj is UserSCAModelItem => - isSKofType.stripeConnectAccount(obj?.sk), - isUserSubscription: (obj: SkTypeGuardArg): obj is UserSubModelItem => - isSKofType.subscription(obj?.sk), - isWorkOrder: (obj: SkTypeGuardArg): obj is WorkOrderItem => isSKofType.workOrder(obj?.sk), + isContact: (obj: SkTypeGuardArg): obj is ContactItem => isSKofType.contact(obj.sk), + isInvoice: (obj: SkTypeGuardArg): obj is InvoiceItem => isSKofType.invoice(obj.sk), + isUser: (obj: SkTypeGuardArg): obj is UserItem => isSKofType.user(obj.sk), + isUserSCA: (obj: SkTypeGuardArg): obj is UserSCAModelItem => isSKofType.sca(obj.sk), + isUserSubscription: (obj: SkTypeGuardArg): obj is UserSubModelItem => isSKofType.subscription(obj.sk), + isWorkOrder: (obj: SkTypeGuardArg): obj is WorkOrderItem => isSKofType.workOrder(obj.sk), + isPwResetToken: (obj: SkTypeGuardArg): obj is PwResetTokenItem => isSKofType.pwResetToken(obj.sk), }; -export interface SkTypeGuardArg { +interface SkTypeGuardArg { sk?: string; - [K: PropertyKey]: any; + [K: PropertyKey]: unknown; } diff --git a/src/models/ddbTable.ts b/src/models/ddbTable.ts index 9e750758..aa0fcbd8 100644 --- a/src/models/ddbTable.ts +++ b/src/models/ddbTable.ts @@ -15,7 +15,7 @@ export const ddbTable = new Table({ required: true, isRangeKey: true, index: { - name: "Overloaded_SK_GSI", // For relational queryies using "sk" as the hash key + name: "Overloaded_SK_GSI", // For queries using "sk" as the hash key rangeKey: "data", global: true, project: true, // all attributes @@ -26,8 +26,8 @@ export const ddbTable = new Table({ type: "string", required: true, index: { - name: "Overloaded_Data_GSI", // For relational queries using "data" as the hash key - rangeKey: "sk", // WO query "WorkOrdersAssignedToUser" uses this GSI SK + name: "Overloaded_Data_GSI", // For queries using "data" as the hash key + rangeKey: "sk", global: true, project: true, // all attributes throughput: { read: 5, write: 5 }, @@ -35,10 +35,10 @@ export const ddbTable = new Table({ }, } as const, ddbClient: { - region: ENV.AWS.REGION, - ...(ENV.AWS?.DYNAMODB_ENDPOINT && { endpoint: ENV.AWS.DYNAMODB_ENDPOINT }), + region: ENV.AWS.DYNAMODB_REGION, + ...(ENV.AWS.DYNAMODB_ENDPOINT && { endpoint: ENV.AWS.DYNAMODB_ENDPOINT }), // dynamodb-local is used in dev - ...(ENV.AWS.REGION === "local" && { + ...(ENV.IS_DEV && { credentials: { accessKeyId: "local", secretAccessKey: "local", diff --git a/src/routers/adminRouter.ts b/src/routers/adminRouter.ts deleted file mode 100644 index 32fa8d6a..00000000 --- a/src/routers/adminRouter.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { hasKey } from "@nerdware/ts-type-safety-utils"; -import express from "express"; -import { logger } from "@/utils/logger.js"; -import type { Request } from "express"; -import type { JsonObject } from "type-fest"; - -/** - * This router handles all `/api/admin` request paths: - * - `/api/admin/healthcheck` - * - `/api/admin/csp-violation` - * - * Example CSP-violation report: - * ```json - * { - * "csp-report": { - * "document-uri": "http://localhost:5500/", - * "referrer": "", - * "violated-directive": "script-src-elem", - * "effective-directive": "script-src-elem", - * "original-policy": "default-src 'self'; font-src 'self'; script-src 'self'; style-src 'self'; frame-src 'self'; report-uri /__cspreport__;", - * "disposition": "report", - * "blocked-uri": "inline", - * "line-number": 58, - * "source-file": "http://localhost:5500/", - * "status-code": 200, - * "script-sample": "" - * } - * } - * ``` - */ -export const adminRouter = express.Router(); - -adminRouter.use("/healthcheck", (req, res) => res.json({ message: "SUCESS" })); - -adminRouter.use( - "/csp-violation", - express.json({ - type: ["application/json", "application/csp-report", "application/reports+json"], - }), - (req: Request, res) => { - // Get `req.body["csp-report"]` - if not present, use the entire `req.body` object: - const report = hasKey(req.body, "csp-report") ? req.body["csp-report"] : { ...req.body }; - logger.security(report, "CSP VIOLATION REPORT"); - res.end(); - } -); diff --git a/src/routers/authRouter.ts b/src/routers/authRouter.ts deleted file mode 100644 index 1907fd24..00000000 --- a/src/routers/authRouter.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { - sanitizeHandle, - isValidHandle, - sanitizePhone, - isValidPhone, - sanitizeEmail, - isValidEmail, - sanitizePassword, - isValidPassword, -} from "@nerdware/ts-string-helpers"; -import { hasKey } from "@nerdware/ts-type-safety-utils"; -import express from "express"; -import { - findUserByEmail, - userLoginShouldExist, - userLoginShouldNotExist, - registerNewUser, - validateLogin, - getUserFromAuthHeaderToken, - parseGoogleIDToken, - updateExpoPushToken, - queryUserItems, - checkSubscriptionStatus, - checkOnboardingStatus, - generateAuthToken, -} from "@/middleware"; -import { sanitizeAndValidateRequestBody } from "@/middleware/helpers.js"; -import type { RequestBodyFieldsSchema, RequestBodyValidatorFn } from "@/middleware/helpers.js"; - -/** - * This router handles all `/api/auth` request paths: - * - `/api/auth/register` - * - `/api/auth/login` - * - `/api/auth/google-token` - * - `/api/auth/token` - */ -export const authRouter = express.Router(); - -/** - * A {@link RequestBodyFieldsSchema} that configures sanitzation and - * validation for request body parameters used in auth routes. - */ -export const LOGIN_REQ_BODY_FIELDS_SCHEMA = { - email: { - required: true, - type: "string", - sanitize: sanitizeEmail, - validate: isValidEmail, - }, - password: { - required: false, - type: "string", - sanitize: sanitizePassword, - validate: isValidPassword, - }, - googleIDToken: { - required: false, - type: "string", - // The Google JWT includes alphanumerics, as well as "-", ".", and "_" chars. - // Note that in the below regex patterns, "-" is escaped so as to not create character ranges. - sanitize: (value) => value.replace(/[^a-zA-Z0-9+/\-._=]/g, ""), - validate: (value) => /^[a-zA-Z0-9+/\-._]+={0,3}$/.test(value), - }, -} as const satisfies RequestBodyFieldsSchema; - -/** - * A {@link RequestBodyValidatorFn} that asserts that the request body - * contains either a password or Google OAuth2 ID token. - */ -export const requirePasswordOrGoogleOAuth: RequestBodyValidatorFn = (reqBody) => { - if (!hasKey(reqBody, "password") && !hasKey(reqBody, "googleIDToken")) { - throw new Error("Invalid registration credentials"); - } -}; - -authRouter.post( - "/register", - sanitizeAndValidateRequestBody({ - requestBodySchema: { - ...LOGIN_REQ_BODY_FIELDS_SCHEMA, - handle: { - required: true, - type: "string", - sanitize: sanitizeHandle, - validate: isValidHandle, - }, - phone: { - required: false, - type: "string", - sanitize: sanitizePhone, - validate: isValidPhone, - }, - }, - validateRequestBody: requirePasswordOrGoogleOAuth, - }), - parseGoogleIDToken, // does nothing for local-auth users - findUserByEmail, - userLoginShouldNotExist, - registerNewUser -); - -authRouter.post( - "/login", - sanitizeAndValidateRequestBody({ - requestBodySchema: LOGIN_REQ_BODY_FIELDS_SCHEMA, - validateRequestBody: requirePasswordOrGoogleOAuth, - }), - parseGoogleIDToken, // does nothing for local-auth users - findUserByEmail, - userLoginShouldExist, - validateLogin, - queryUserItems, - updateExpoPushToken, - checkSubscriptionStatus, - checkOnboardingStatus -); - -authRouter.post( - "/google-token", - sanitizeAndValidateRequestBody({ - requestBodySchema: { - googleIDToken: { - ...LOGIN_REQ_BODY_FIELDS_SCHEMA.googleIDToken, - required: true, - }, - }, - }), - parseGoogleIDToken, - findUserByEmail, - userLoginShouldExist, - validateLogin, - queryUserItems, - updateExpoPushToken, - checkSubscriptionStatus, - checkOnboardingStatus -); - -authRouter.post( - "/token", - getUserFromAuthHeaderToken, - queryUserItems, - updateExpoPushToken, - checkSubscriptionStatus, - checkOnboardingStatus -); - -authRouter.use(generateAuthToken); diff --git a/src/routers/connectRouter.ts b/src/routers/connectRouter.ts deleted file mode 100644 index 5a96de59..00000000 --- a/src/routers/connectRouter.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { sanitizeURL, isValidURL } from "@nerdware/ts-string-helpers"; -import express from "express"; -import { getUserFromAuthHeaderToken, createAccountLink, createDashboardLink } from "@/middleware"; -import { sanitizeAndValidateRequestBody } from "@/middleware/helpers.js"; - -/** - * This router handles all `/api/connect` request paths: - * - `/api/connect/account-link` - * - `/api/connect/dashboard-link` - */ -export const connectRouter = express.Router(); - -connectRouter.use(getUserFromAuthHeaderToken); - -connectRouter.post( - "/account-link", - sanitizeAndValidateRequestBody({ - requestBodySchema: { - returnURL: { - required: true, - type: "string", - sanitize: sanitizeURL, - validate: isValidURL, - }, - }, - }), - createAccountLink -); - -connectRouter.get("/dashboard-link", createDashboardLink); diff --git a/src/routers/index.ts b/src/routers/index.ts deleted file mode 100644 index 8a1a192b..00000000 --- a/src/routers/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./adminRouter.js"; -export * from "./authRouter.js"; -export * from "./connectRouter.js"; -export * from "./subscriptionsRouter.js"; -export * from "./webhooksRouter.js"; diff --git a/src/routers/subscriptionsRouter.ts b/src/routers/subscriptionsRouter.ts deleted file mode 100644 index aab7016d..00000000 --- a/src/routers/subscriptionsRouter.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { - sanitizeAlphabetic, - isValidAlphabetic, - sanitizeID, - sanitizeURL, - isValidURL, -} from "@nerdware/ts-string-helpers"; -import express from "express"; -import { pricesCache } from "@/lib/cache/pricesCache.js"; -import { isValidStripeID } from "@/lib/stripe/isValidStripeID.js"; -import { - checkPromoCode, - createCustomerPortalLink, - findOrCreateStripeSubscription, - generateAuthToken, - getUserFromAuthHeaderToken, -} from "@/middleware"; -import { sanitizeAndValidateRequestBody } from "@/middleware/helpers.js"; -import { UserSubscription } from "@/models/UserSubscription/UserSubscription.js"; - -/** - * This router handles all `/api/subscriptions` request paths: - * - `/api/subscriptions/check-promo-code` - * - `/api/subscriptions/submit-payment` - * - `/api/subscriptions/customer-portal` - */ -export const subscriptionsRouter = express.Router(); - -subscriptionsRouter.use(getUserFromAuthHeaderToken); - -subscriptionsRouter.post( - "/check-promo-code", - sanitizeAndValidateRequestBody({ - requestBodySchema: { - promoCode: { - required: true, - type: "string", - sanitize: sanitizeAlphabetic, - validate: isValidAlphabetic, - }, - }, - }), - checkPromoCode -); - -subscriptionsRouter.post( - "/submit-payment", - sanitizeAndValidateRequestBody({ - requestBodySchema: { - selectedSubscription: { - required: true, - type: "string", - sanitize: sanitizeAlphabetic, - validate: pricesCache.has, - }, - paymentMethodID: { - required: true, - type: "string", - sanitize: sanitizeID, - validate: isValidStripeID.paymentMethod, - }, - promoCode: { - required: false, - type: "string", - sanitize: sanitizeAlphabetic, - validate: UserSubscription.validatePromoCode, - }, - }, - }), - findOrCreateStripeSubscription, - generateAuthToken -); - -subscriptionsRouter.post( - "/customer-portal", - sanitizeAndValidateRequestBody({ - requestBodySchema: { - returnURL: { - required: true, - type: "string", - sanitize: sanitizeURL, - validate: isValidURL, - }, - }, - }), - createCustomerPortalLink -); diff --git a/src/routers/webhooksRouter.ts b/src/routers/webhooksRouter.ts deleted file mode 100644 index 3901575c..00000000 --- a/src/routers/webhooksRouter.ts +++ /dev/null @@ -1,14 +0,0 @@ -import express from "express"; -import { handleStripeWebhookEvent } from "@/middleware/stripeWebhooks"; - -/** - * This router handles all `/api/webhooks` request paths: - * - `/api/webhooks/stripe` - */ -export const webhooksRouter = express.Router(); - -webhooksRouter.use( - "/stripe", - express.raw({ type: "application/json" }), - handleStripeWebhookEvent // prettier-ignore -); diff --git a/src/routes/README.md b/src/routes/README.md new file mode 100644 index 00000000..d62f5288 --- /dev/null +++ b/src/routes/README.md @@ -0,0 +1,15 @@ +# REST API Routes + +> [!NOTE] +> +> This directory contains the application's path-based routers. +> +> Each router is responsible for handling the requests to a specific REST API endpoint. + +| Router | REST API Path | +| :------------------------------------------ | :--------------------- | +| [`adminRouter`](./admin.ts) | `/api/admin/*` | +| [`authRouter`](./auth.ts) | `/api/auth/*` | +| [`connectRouter`](./connect.ts) | `/api/connect/*` | +| [`subscriptionsRouter`](./subscriptions.ts) | `/api/subscriptions/*` | +| [`webhooksRouter`](./webhooks.ts) | `/api/webhooks/*` | diff --git a/src/routes/admin.ts b/src/routes/admin.ts new file mode 100644 index 00000000..8524103c --- /dev/null +++ b/src/routes/admin.ts @@ -0,0 +1,8 @@ +import express from "express"; +import { AdminController } from "@/controllers/AdminController"; + +export const adminRouter = express.Router(); + +adminRouter.get("/healthcheck", AdminController.healthcheck); + +adminRouter.post("/csp-violation", AdminController.cspViolation); diff --git a/src/routes/auth.ts b/src/routes/auth.ts new file mode 100644 index 00000000..dadbd645 --- /dev/null +++ b/src/routes/auth.ts @@ -0,0 +1,16 @@ +import express from "express"; +import { AuthController } from "@/controllers/AuthController"; + +export const authRouter = express.Router(); + +authRouter.post("/register", AuthController.registerNewUser); + +authRouter.post("/login", AuthController.login); + +authRouter.post("/google-token", AuthController.googleTokenLogin); + +authRouter.post("/password-reset-init", AuthController.pwResetInit); + +authRouter.post("/password-reset", AuthController.passwordReset); + +authRouter.post("/token", AuthController.refreshAuthToken); diff --git a/src/routes/connect.ts b/src/routes/connect.ts new file mode 100644 index 00000000..d11fa52b --- /dev/null +++ b/src/routes/connect.ts @@ -0,0 +1,8 @@ +import express from "express"; +import { ConnectController } from "@/controllers/ConnectController"; + +export const connectRouter = express.Router(); + +connectRouter.post("/account-link", ConnectController.createAccountLink); + +connectRouter.get("/dashboard-link", ConnectController.createDashboardLink); diff --git a/src/routes/index.ts b/src/routes/index.ts new file mode 100644 index 00000000..42f56a59 --- /dev/null +++ b/src/routes/index.ts @@ -0,0 +1,5 @@ +export * from "./admin.js"; +export * from "./auth.js"; +export * from "./connect.js"; +export * from "./subscriptions.js"; +export * from "./webhooks.js"; diff --git a/src/routes/subscriptions.ts b/src/routes/subscriptions.ts new file mode 100644 index 00000000..80bc4425 --- /dev/null +++ b/src/routes/subscriptions.ts @@ -0,0 +1,10 @@ +import express from "express"; +import { SubscriptionsController as SubsController } from "@/controllers/SubscriptionsController"; + +export const subscriptionsRouter = express.Router(); + +subscriptionsRouter.post("/check-promo-code", SubsController.checkPromoCode); + +subscriptionsRouter.post("/submit-payment", SubsController.submitPayment); + +subscriptionsRouter.post("/customer-portal", SubsController.createCustomerBillingPortalLink); diff --git a/src/routes/webhooks.ts b/src/routes/webhooks.ts new file mode 100644 index 00000000..bc357221 --- /dev/null +++ b/src/routes/webhooks.ts @@ -0,0 +1,6 @@ +import express from "express"; +import { WebhooksController } from "@/controllers/WebhooksController"; + +export const webhooksRouter = express.Router(); + +webhooksRouter.use("/stripe", WebhooksController.stripeWebhooks); diff --git a/src/server/env/__mocks__/index.ts b/src/server/env/__mocks__/index.ts index 555190b8..0e9e6c07 100644 --- a/src/server/env/__mocks__/index.ts +++ b/src/server/env/__mocks__/index.ts @@ -8,41 +8,57 @@ import { createEnvObject } from "../helpers.js"; const { npm_package_version, - NODE_ENV = "test", + // SERVER VITE_PROTOCOL: PROTOCOL = "http", VITE_DOMAIN: DOMAIN = "localhost", VITE_PORT: PORT = "0", + // WEB CLIENT + VITE_WEB_CLIENT_URL: WEB_CLIENT_URL = "http://localhost:3000", + // AWS VITE_AWS_REGION: AWS_REGION = "local", + VITE_DYNAMODB_REGION: DYNAMODB_REGION = "local", VITE_DYNAMODB_TABLE_NAME: DYNAMODB_TABLE_NAME = "fixit-db-test", VITE_DYNAMODB_ENDPOINT: DYNAMODB_ENDPOINT = "http://localhost:8000", + VITE_PINPOINT_PROJECT_ID: PINPOINT_PROJECT_ID = "TestTestTest", + VITE_SES_EMAIL_ADDRESS: SES_EMAIL_ADDRESS = "test@test.test", + // AUTH VITE_JWT_PRIVATE_KEY: JWT_PRIVATE_KEY = "TestTestTest", VITE_JWT_ALGORITHM: JWT_ALGORITHM = "HS256", VITE_JWT_ISSUER: JWT_ISSUER = "TestTestTest", VITE_JWT_EXPIRES_IN: JWT_EXPIRES_IN = "5m", VITE_BCRYPT_SALT_ROUNDS: BCRYPT_SALT_ROUNDS = "10", - VITE_SENTRY_DSN: SENTRY_DSN = "TestTestTest", + VITE_UUID_NAMESPACE: UUID_NAMESPACE = "aaaaaaaa-aaaa-5aaa-8aaa-aaaaaaaaaaaa", // 5=version, 8=variant + // SENTRY + VITE_SENTRY_DSN: SENTRY_DSN, + // STRIPE VITE_STRIPE_API_VERSION: STRIPE_API_VERSION = "2022-08-01", VITE_STRIPE_PUBLISHABLE_KEY: STRIPE_PUBLISHABLE_KEY = "pk_fake_TestTestTest", VITE_STRIPE_SECRET_KEY: STRIPE_SECRET_KEY = "sk_fake_TestTestTest", VITE_STRIPE_WEBHOOKS_SECRET: STRIPE_WEBHOOKS_SECRET = "whsec_TestTestTest", + // GOOGLE VITE_GOOGLE_OAUTH_CLIENT_ID: GOOGLE_OAUTH_CLIENT_ID = "TestTestTest.apps.googleusercontent.com", VITE_GOOGLE_OAUTH_CLIENT_SECRET: GOOGLE_OAUTH_CLIENT_SECRET = "TestTestTest", } = process.env; // eslint-disable-line node/no-process-env export const ENV = createEnvObject({ ...(!!npm_package_version && { npm_package_version }), - NODE_ENV, + NODE_ENV: "test", PROTOCOL, DOMAIN, PORT, + WEB_CLIENT_URL, AWS_REGION, + DYNAMODB_REGION, DYNAMODB_TABLE_NAME, DYNAMODB_ENDPOINT, + PINPOINT_PROJECT_ID, + SES_EMAIL_ADDRESS, JWT_PRIVATE_KEY, JWT_ALGORITHM, JWT_ISSUER, JWT_EXPIRES_IN, BCRYPT_SALT_ROUNDS, + UUID_NAMESPACE, SENTRY_DSN, STRIPE_API_VERSION, STRIPE_PUBLISHABLE_KEY, diff --git a/src/server/env/helpers.ts b/src/server/env/helpers.ts index bd2add59..29636952 100644 --- a/src/server/env/helpers.ts +++ b/src/server/env/helpers.ts @@ -1,5 +1,3 @@ -import type { Algorithm } from "jsonwebtoken"; - /** * @returns a readonly `ENV` object with environment variables used throughout the application. * @throws an error if required env vars are missing, or if certain env var values are invalid. @@ -7,22 +5,34 @@ import type { Algorithm } from "jsonwebtoken"; export const createEnvObject = ({ npm_package_version, NODE_ENV, + // SERVER PROTOCOL, DOMAIN, PORT, + // WEB CLIENT + WEB_CLIENT_URL, + // AWS AWS_REGION, + DYNAMODB_REGION, DYNAMODB_TABLE_NAME, DYNAMODB_ENDPOINT, + PINPOINT_PROJECT_ID, + SES_EMAIL_ADDRESS, + // AUTH JWT_PRIVATE_KEY, JWT_ALGORITHM, JWT_ISSUER, JWT_EXPIRES_IN, BCRYPT_SALT_ROUNDS, + UUID_NAMESPACE, + // SENTRY SENTRY_DSN, + // STRIPE STRIPE_API_VERSION, STRIPE_PUBLISHABLE_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOKS_SECRET, + // GOOGLE GOOGLE_OAUTH_CLIENT_ID, GOOGLE_OAUTH_CLIENT_SECRET, }: typeof process.env) => { @@ -32,14 +42,17 @@ export const createEnvObject = ({ !PROTOCOL || !DOMAIN || !PORT || + !WEB_CLIENT_URL || !AWS_REGION || !DYNAMODB_TABLE_NAME || + !PINPOINT_PROJECT_ID || + !SES_EMAIL_ADDRESS || !JWT_PRIVATE_KEY || !JWT_ALGORITHM || !JWT_ISSUER || !JWT_EXPIRES_IN || !BCRYPT_SALT_ROUNDS || - !SENTRY_DSN || + !UUID_NAMESPACE || !STRIPE_API_VERSION || !STRIPE_PUBLISHABLE_KEY || !STRIPE_SECRET_KEY || @@ -47,26 +60,25 @@ export const createEnvObject = ({ !GOOGLE_OAUTH_CLIENT_ID || !GOOGLE_OAUTH_CLIENT_SECRET ) { - throw new Error("Missing required environment variables."); + throw new Error("Missing required environment variables"); } - // Ensure the provided JWT_ALGORITHM is supported - if (!/^[HRE]S(256|384|512)$/.test(JWT_ALGORITHM)) { - throw new Error("Unsupported JWT_ALGORITHM"); - } + if (!/^(development|test|staging|production)$/.test(NODE_ENV)) + throw new Error("Unknown NODE_ENV"); const API_BASE_URL = `${PROTOCOL}://${DOMAIN}`; return { NODE_ENV, - IS_PROD: /^prod/i.test(NODE_ENV), - IS_DEPLOYED_ENV: /^(prod|staging)/i.test(NODE_ENV), + IS_DEV: NODE_ENV === "development", + IS_PROD: NODE_ENV === "production", + IS_DEPLOYED_ENV: /^(production|staging)$/.test(NODE_ENV), CONFIG: { ...(npm_package_version && { PROJECT_VERSION: `v${npm_package_version}` }), - TIMEZONE: new Date().toString().match(/([A-Z]+[+-][0-9]+.*)/)?.[1] ?? "-", + TIMEZONE: new Date().toString().match(/([A-Z]+[+-][0-9]+.*)/)?.[0] ?? "-", PROTOCOL, DOMAIN, - PORT, + PORT: Number(PORT), API_BASE_URL, API_FULL_URL: `${API_BASE_URL}:${PORT}/api`, OS_PLATFORM: process.platform, @@ -74,18 +86,25 @@ export const createEnvObject = ({ NODE_VERSION: process.version, CWD: process.cwd(), }, + WEB_CLIENT: { + URL: WEB_CLIENT_URL, + }, AWS: { REGION: AWS_REGION, + DYNAMODB_REGION: DYNAMODB_REGION ?? AWS_REGION, DYNAMODB_TABLE_NAME, - ...(DYNAMODB_ENDPOINT && { DYNAMODB_ENDPOINT }), + DYNAMODB_ENDPOINT, + PINPOINT_PROJECT_ID, + SES_EMAIL_ADDRESS, }, JWT: { PRIVATE_KEY: JWT_PRIVATE_KEY, - ALGORITHM: JWT_ALGORITHM as Algorithm, + ALGORITHM: JWT_ALGORITHM, ISSUER: JWT_ISSUER, EXPIRES_IN: JWT_EXPIRES_IN, }, - BCRYPT_SALT_ROUNDS: parseInt(BCRYPT_SALT_ROUNDS, 10), + BCRYPT_SALT_ROUNDS: Number(BCRYPT_SALT_ROUNDS), + UUID_NAMESPACE, SENTRY_DSN, STRIPE: { API_VERSION: STRIPE_API_VERSION, diff --git a/src/server/init.ts b/src/server/init.ts index 3baa494d..995a7c38 100644 --- a/src/server/init.ts +++ b/src/server/init.ts @@ -2,7 +2,7 @@ import "./initSentry.js"; import "./processEventHandlers.js"; import "./logStartupInfo.js"; -/* The imports in this file achieve the following: +/* The side-effect imports in this file achieve the following: sentry Initialize Sentry processHandlers Initialize NodeJS process event handlers diff --git a/src/server/initSentry.ts b/src/server/initSentry.ts index fd107685..a1bb05ed 100644 --- a/src/server/initSentry.ts +++ b/src/server/initSentry.ts @@ -1,12 +1,16 @@ import * as Sentry from "@sentry/node"; import { ENV } from "@/server/env"; -if (/^(dev|staging|prod)/i.test(ENV.NODE_ENV) && !!ENV?.SENTRY_DSN) { +if (ENV.SENTRY_DSN) { Sentry.init({ enabled: true, dsn: ENV.SENTRY_DSN, environment: ENV.NODE_ENV, tracesSampleRate: 1.0, - ...(!!ENV.CONFIG?.PROJECT_VERSION && { release: ENV.CONFIG.PROJECT_VERSION }), + ...(ENV.CONFIG.PROJECT_VERSION && { + release: ENV.CONFIG.PROJECT_VERSION, + }), }); +} else if (ENV.IS_DEPLOYED_ENV) { + throw new Error("Unable to initialize Sentry in deployed env: Missing SENTRY_DSN"); } diff --git a/src/server/logStartupInfo.ts b/src/server/logStartupInfo.ts index 24d13a8c..a9f56c5f 100644 --- a/src/server/logStartupInfo.ts +++ b/src/server/logStartupInfo.ts @@ -3,15 +3,15 @@ import { logger } from "@/utils/logger.js"; const { NODE_ENV, - AWS: { REGION }, + AWS, CONFIG: { PROJECT_VERSION, TIMEZONE, OS_PLATFORM, PID, NODE_VERSION, API_BASE_URL, PORT, CWD }, } = ENV; -if (ENV.NODE_ENV === "development") { +if (ENV.IS_DEV) { logger.server( `(SERVER STARTUP) 🚀 fixit-api ${PROJECT_VERSION || ""} - App Env ...... ${NODE_ENV} - AWS Region ... ${REGION} + NODE_ENV...... ${NODE_ENV} + AWS Region ... ${AWS.REGION} Timezone ..... ${TIMEZONE} Platform ..... ${OS_PLATFORM} PID .......... ${PID} diff --git a/src/server/processEventHandlers.ts b/src/server/processEventHandlers.ts index 976a6d12..bda67561 100644 --- a/src/server/processEventHandlers.ts +++ b/src/server/processEventHandlers.ts @@ -1,34 +1,17 @@ import { getTypeSafeError } from "@nerdware/ts-type-safety-utils"; import { logger } from "@/utils/logger.js"; -Object.entries({ - uncaughtException: 1, - unhandledRejection: 2, -}).forEach(([errEvent, errExitCode]) => { - process.on(errEvent, (error) => { - logger.error(getTypeSafeError(error)); - process.exitCode = errExitCode; - }); +process.on("uncaughtException", (error: Error) => { + logger.error(error, `(Process Event "uncaughtException")`); }); -["SIGINT", "SIGTERM", "SIGQUIT"].forEach((signalType) => { - // eslint-disable-next-line no-process-exit - process.once(signalType, () => process.exit(process?.exitCode ?? 0)); +process.on("unhandledRejection", (rejectionReason: unknown) => { + const error = getTypeSafeError(rejectionReason); + logger.error(error, `(Process Event "unhandledRejection")`); + throw error; }); process.on("exit", (exitCode) => { - // If zero, exit normally - if (exitCode === 0) { - logger.server("Exiting Process (EXIT_CODE: 0)"); - } else { - // Else, log the error type - const errorDescription = - exitCode === 1 - ? "UNCAUGHT_EXCEPTION" - : exitCode === 2 - ? "UNHANDLED_REJECTION" - : "UNHANDLED_ERROR_EXIT_CODE"; - - logger.error(`EXITING PROCESS: ${errorDescription} (EXIT_CODE: ${exitCode})`); - } + const loggerFn = exitCode === 0 ? logger.server : logger.error; + loggerFn(`(Process Event "exit") EXIT_CODE: ${exitCode}`); }); diff --git a/src/services/AccountService/createCustomerBillingPortalLink.ts b/src/services/AccountService/createCustomerBillingPortalLink.ts new file mode 100644 index 00000000..69a94682 --- /dev/null +++ b/src/services/AccountService/createCustomerBillingPortalLink.ts @@ -0,0 +1,20 @@ +import { stripe } from "@/lib/stripe/stripeClient.js"; +import type { AuthTokenPayload } from "@/types/open-api.js"; + +/** + * ### AccountService: createCustomerBillingPortalLink + * + * @returns a Stripe BillingPortal.Session object. + */ +export const createCustomerBillingPortalLink = async ({ + authenticatedUser, + returnURL, +}: { + authenticatedUser: AuthTokenPayload; + returnURL: string; +}) => { + return await stripe.billingPortal.sessions.create({ + customer: authenticatedUser.stripeCustomerID, + return_url: returnURL, + }); +}; diff --git a/src/services/AccountService/createDashboardLink.ts b/src/services/AccountService/createDashboardLink.ts new file mode 100644 index 00000000..d9d691a4 --- /dev/null +++ b/src/services/AccountService/createDashboardLink.ts @@ -0,0 +1,18 @@ +import { stripe } from "@/lib/stripe/stripeClient.js"; +import type { AuthTokenPayload } from "@/types/open-api.js"; + +/** + * ### AccountService: createStripeConnectAccountLink + * + * The `url` of the returned object provides customers with a link to access + * their dashboard/Stripe account. + * + * @returns a Stripe LoginLink object. + */ +export const createDashboardLink = async ({ + authenticatedUser, +}: { + authenticatedUser: AuthTokenPayload; +}) => { + return await stripe.accounts.createLoginLink(authenticatedUser.stripeConnectAccount.id); +}; diff --git a/src/services/AccountService/createStripeConnectAccountLink.ts b/src/services/AccountService/createStripeConnectAccountLink.ts new file mode 100644 index 00000000..459e5ad5 --- /dev/null +++ b/src/services/AccountService/createStripeConnectAccountLink.ts @@ -0,0 +1,22 @@ +import { stripe } from "@/lib/stripe/stripeClient.js"; +import type { AuthTokenPayload } from "@/types/open-api.js"; + +/** + * ### AccountService: createStripeConnectAccountLink + * + * @returns a Stripe AccountLink object. + */ +export const createStripeConnectAccountLink = async ({ + authenticatedUser, + returnURL, +}: { + authenticatedUser: AuthTokenPayload; + returnURL: string; +}) => { + return await stripe.accountLinks.create({ + account: authenticatedUser.stripeConnectAccount.id, + return_url: `${returnURL}?connect-return`, + refresh_url: `${returnURL}?connect-refresh`, + type: "account_onboarding", + }); +}; diff --git a/src/services/AccountService/index.ts b/src/services/AccountService/index.ts new file mode 100644 index 00000000..6ea284fc --- /dev/null +++ b/src/services/AccountService/index.ts @@ -0,0 +1,15 @@ +import { createCustomerBillingPortalLink } from "./createCustomerBillingPortalLink.js"; +import { createDashboardLink } from "./createDashboardLink.js"; +import { createStripeConnectAccountLink } from "./createStripeConnectAccountLink.js"; + +/** + * #### AccountService + * + * This object contains methods which implement business logic related to + * user account management. + */ +export const AccountService = { + createCustomerBillingPortalLink, + createDashboardLink, + createStripeConnectAccountLink, +} as const; diff --git a/src/utils/AuthToken.test.ts b/src/services/AuthService/AuthToken.test.ts similarity index 76% rename from src/utils/AuthToken.test.ts rename to src/services/AuthService/AuthToken.test.ts index 27a67772..b909a643 100644 --- a/src/utils/AuthToken.test.ts +++ b/src/services/AuthService/AuthToken.test.ts @@ -16,6 +16,7 @@ const MOCK_AUTH_TOKEN_USER_DATA = { chargesEnabled: true, payoutsEnabled: true, }, + subscription: null, createdAt: new Date(), updatedAt: new Date(), }; @@ -28,7 +29,7 @@ describe("AuthToken", () => { }); test("throws an error when called with invalid ctor args", () => { - expect(() => new AuthToken({} as any)).toThrow(`"subject" must be a string`); + expect(() => new AuthToken({} as any)).toThrow(/invalid/i); }); }); @@ -39,10 +40,10 @@ describe("AuthToken", () => { }); }); - describe("AuthToken.validateAndDecodeAuthToken()", () => { + describe("AuthToken.validateAndDecode()", () => { test("returns decoded auth token payload when called with a valid token arg", async () => { const authToken = new AuthToken(MOCK_AUTH_TOKEN_USER_DATA); - const result = await AuthToken.validateAndDecodeAuthToken(authToken.toString()); + const result = await AuthToken.validateAndDecode(authToken.toString()); expect(result).toStrictEqual({ ...MOCK_AUTH_TOKEN_USER_DATA, createdAt: expect.toBeValidDate(), @@ -64,15 +65,4 @@ describe("AuthToken", () => { }); }); }); - - describe("AuthToken.stripInternalJwtPayloadFields()", () => { - test("returns the payload with internal JWT payload fields stripped", () => { - const result = AuthToken.stripInternalJwtPayloadFields({ - ...MOCK_AUTH_TOKEN_USER_DATA, - aud: "test", - }); - expect(result).toStrictEqual(MOCK_AUTH_TOKEN_USER_DATA); - expect(result).not.toHaveProperty("aud"); - }); - }); }); diff --git a/src/services/AuthService/AuthToken.ts b/src/services/AuthService/AuthToken.ts new file mode 100644 index 00000000..e8f378a2 --- /dev/null +++ b/src/services/AuthService/AuthToken.ts @@ -0,0 +1,126 @@ +import { isString } from "@nerdware/ts-type-safety-utils"; +import { AuthError, InternalServerError } from "@/utils/httpErrors.js"; +import { JWT } from "@/utils/jwt.js"; +import type { AuthTokenPayload } from "@/types/open-api.js"; +import type { Request } from "express"; +import type { SetOptional } from "type-fest"; + +/** + * The AuthToken class is responsible for creating, validating, and decoding + * JSON Web Tokens (JWTs) used for authentication in the Fixit API. + */ +export class AuthToken { + /** + * Creates a new AuthToken by signing and encoding a JWT payload using the provided user data. + * @param userData - The user data used to create the JWT payload. + */ + static readonly create = (userData: CreateAuthTokenParams) => new AuthToken(userData); + + /** + * Validates and decodes an encoded auth token. + * @param encodedAuthToken - The encoded auth token to validate and decode. + * @returns The decoded auth token payload. + */ + static readonly validateAndDecode = async (encodedAuthToken: string) => { + return await JWT.validateAndDecode(encodedAuthToken, { + shouldStripInternalFields: true, + decodeErrMsgs: { + TokenExpiredError: "Your login credentials have expired — please sign in again.", + JsonWebTokenError: "Signature verification failed", + default: "Invalid auth token", + }, + }); + }; + + /** + * Extracts a raw auth token string from the "Authorization" header of an incoming request. + */ + static readonly getAuthHeaderToken = (req: R) => { + // Get token from "Authorization" header + let token = req.get("Authorization"); + // Ensure token exists and is a string + if (!token || !isString(token)) throw new AuthError("Invalid auth token"); + // Remove 'Bearer ' from string if present + if (token.startsWith("Bearer ")) token = token.split(" ")[1]!; + return token; + }; + + /** + * Validates the "Authorization" header of an incoming request and returns the decoded payload if valid. + * @param request - The incoming request object. + * @returns The decoded auth token payload. + * @throws Error if the token is invalid. + */ + static readonly getValidatedRequestAuthTokenPayload = async (request: Request) => { + // Get token from "Authorization" header + const token = AuthToken.getAuthHeaderToken(request); + // Validate the token; if valid, returns decoded payload. + return await AuthToken.validateAndDecode(token); + }; + + private readonly encodedTokenValue: string; + + /** + * This method causes `encodedTokenValue` to be returned whenever type coercion occurs. + * + * > Although this method takes precedence over `toString` and `valueOf` whenever + * _**implicit**_ type coercion occurs, it is not called if/when `toString` or + * `valueOf` are called _**explicitly**_. Therefore, all 3 methods are implemented + * here to ensure that the token is always returned when type coercion occurs. + */ + [Symbol.toPrimitive]() { + return this.encodedTokenValue; + } + valueOf() { + return this.encodedTokenValue; + } + toString() { + return this.encodedTokenValue; + } + + /** + * Creates a new AuthToken by signing and encoding a JWT payload using the provided user data. + * @param userData - The user data used to create the JWT payload. + */ + constructor(userData: CreateAuthTokenParams) { + // Destructure the userData + const { + id, handle, email, phone, profile, stripeCustomerID, stripeConnectAccount, subscription, createdAt, updatedAt + } = userData; // prettier-ignore + + // Ensure required fields are present + // prettier-ignore + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!id || !handle || !email || !profile || !stripeCustomerID || !stripeConnectAccount || !createdAt || !updatedAt) + throw new InternalServerError("Invalid user data provided to AuthToken constructor"); + + // Form the token payload using only the permitted fields + const payload: AuthTokenPayload = { + id, + handle, + email, + phone: phone ?? null, + profile, + stripeCustomerID, + stripeConnectAccount: { + id: stripeConnectAccount.id, + detailsSubmitted: stripeConnectAccount.detailsSubmitted, + chargesEnabled: stripeConnectAccount.chargesEnabled, + payoutsEnabled: stripeConnectAccount.payoutsEnabled, + }, + subscription: subscription + ? { + id: subscription.id, + status: subscription.status, + currentPeriodEnd: subscription.currentPeriodEnd, + } + : null, + createdAt, + updatedAt, + }; + + this.encodedTokenValue = JWT.signAndEncode(payload); + } +} + +export type CreateAuthTokenParams = SetOptional; diff --git a/src/services/AuthService/GoogleOAuth2IDToken.ts b/src/services/AuthService/GoogleOAuth2IDToken.ts new file mode 100644 index 00000000..9cb94b59 --- /dev/null +++ b/src/services/AuthService/GoogleOAuth2IDToken.ts @@ -0,0 +1,104 @@ +import { + sanitizeID, + isValidID, + sanitizeEmail, + isValidEmail, + sanitizeURL, + isValidURL, + sanitizeName, + isValidName, +} from "@nerdware/ts-string-helpers"; +import { getTypeSafeError } from "@nerdware/ts-type-safety-utils"; +import { googleOAuth2Client } from "@/lib/googleOAuth2Client"; +import { AuthError } from "@/utils/httpErrors.js"; +import type { UserItem } from "@/models/User"; +import type { TokenPayload as GoogleOAuth2IDTokenPayload } from "google-auth-library"; +import type { Simplify } from "type-fest"; + +/** + * ### GoogleOAuth2IDToken + * + * This object contains methods for parsing and validating Google OAuth2 ID tokens. + */ +export const GoogleOAuth2IDToken = { + /** + * This function validates and parses a Google OAuth2 ID token, including the + * relevant payload fields extracted from it. + * + * > **The structure of Google JWT ID tokens is available here:** + * > https://developers.google.com/identity/gsi/web/reference/js-reference#credential + * + * @see https://developers.google.com/identity/gsi/web/guides/verify-google-id-token + */ + parse: async (rawGoogleIDToken: string): Promise => { + // Initialize variable to hold the token payload: + let tokenPayload: GoogleOAuth2IDTokenPayload | undefined; + + try { + const ticket = await googleOAuth2Client.verifyIdToken({ idToken: rawGoogleIDToken }); + + tokenPayload = ticket.getPayload(); + } catch (err) { + // Re-throw as AuthError + throw new AuthError( + getTypeSafeError(err, { fallBackErrMsg: DEFAULT_GOOGLE_OAUTH_ERR_MSG }).message + ); + } + + if (!tokenPayload) throw new AuthError(DEFAULT_GOOGLE_OAUTH_ERR_MSG); + + const { + email: unsanitized_email, + sub: unsanitized_googleID, + given_name: unsanitized_givenName, + family_name: unsanitized_familyName, + picture: unsanitized_profilePhotoURL, + } = tokenPayload; + + // Ensure the payload includes an `email`: + if (!unsanitized_email) throw new AuthError(DEFAULT_GOOGLE_OAUTH_ERR_MSG); + + // Sanitize the relevant payload fields (optional fields use `let` & default to null if invalid) + + const email = sanitizeEmail(unsanitized_email); + const googleID = sanitizeID(unsanitized_googleID); + let givenName = unsanitized_givenName ? sanitizeName(unsanitized_givenName) : null; + let familyName = unsanitized_familyName ? sanitizeName(unsanitized_familyName) : null; + let profilePhotoUrl = unsanitized_profilePhotoURL + ? sanitizeURL(unsanitized_profilePhotoURL) + : null; + + // Validate the REQUIRED payload fields (if invalid, throw error) + if (!isValidEmail(email) || !isValidID(googleID)) + throw new AuthError(DEFAULT_GOOGLE_OAUTH_ERR_MSG); + + // Validate the OPTIONAL payload fields (if invalid, set to null) + if (givenName && !isValidName(givenName)) givenName = null; + if (familyName && !isValidName(familyName)) familyName = null; + if (profilePhotoUrl && !isValidURL(profilePhotoUrl)) profilePhotoUrl = null; + + return { + email, + googleID, + profile: { + givenName, + familyName, + photoUrl: profilePhotoUrl, + }, + }; + }, +} as const; + +const DEFAULT_GOOGLE_OAUTH_ERR_MSG = "Invalid credentials"; + +/** + * The fields returned by {@link GoogleOAuth2IDToken.parse}. + */ +export type ParsedGoogleOAuth2IDTokenFields = Simplify< + Pick & + Pick & { + profile: Simplify< + Required> + >; + } +>; diff --git a/src/services/AuthService/authenticateUserViaLoginCredentials.ts b/src/services/AuthService/authenticateUserViaLoginCredentials.ts new file mode 100644 index 00000000..1e9cb1fa --- /dev/null +++ b/src/services/AuthService/authenticateUserViaLoginCredentials.ts @@ -0,0 +1,46 @@ +import { User, type UserItem } from "@/models/User"; +import { AuthError, InternalServerError } from "@/utils/httpErrors.js"; +import { passwordHasher } from "@/utils/passwordHasher.js"; +import type { LoginParams } from "@/models/UserLogin"; + +/** + * ### AuthService: authenticateUserViaLoginCredentials + * + * This function is used to authenticate a user via login credentials. + * @returns The authenticated UserItem. + */ +export const authenticateUserViaLoginCredentials = async ({ + email, + password, + googleID, +}: Pick & Partial): Promise => { + // Ensure the user exists + const [user] = await User.query({ where: { email }, limit: 1 }); + + if (!user) throw new AuthError("User not found"); + + // Check login type + if (user.login.type === "LOCAL" && user.login.passwordHash) { + // Ensure password was provided + if (!password) throw new AuthError(ERR_MSG_USE_DIFFERENT_LOGIN_METHOD); + // Validate the password + const isValidPassword = await passwordHasher.validate(password, user.login.passwordHash); + if (!isValidPassword) throw new AuthError("Invalid email or password"); + // + } else if (user.login.type === "GOOGLE_OAUTH" && user.login.googleID) { + // Ensure googleIDToken was provided + if (!googleID) throw new AuthError(ERR_MSG_USE_DIFFERENT_LOGIN_METHOD); + // Validate the googleID + const isValidGoogleIDToken = googleID === user.login.googleID; + if (!isValidGoogleIDToken) throw new AuthError("Invalid OAuth credentials"); + // + } else { + // Future proofing: if the login type is not yet handled, throw an error + throw new InternalServerError("Invalid login credentials"); + } + + return user; +}; + +const ERR_MSG_USE_DIFFERENT_LOGIN_METHOD = + "Your account was created with a different login method. Please use that method to sign in."; diff --git a/src/services/AuthService/index.ts b/src/services/AuthService/index.ts new file mode 100644 index 00000000..d81d449a --- /dev/null +++ b/src/services/AuthService/index.ts @@ -0,0 +1,35 @@ +import { AuthToken } from "./AuthToken.js"; +import { GoogleOAuth2IDToken } from "./GoogleOAuth2IDToken.js"; +import { authenticateUserViaLoginCredentials } from "./authenticateUserViaLoginCredentials.js"; +import { preFetchAndSyncUserItems } from "./preFetchAndSyncUserItems.js"; +import { resetPassword } from "./resetPassword.js"; +import { sendPasswordResetEmail } from "./sendPasswordResetEmail.js"; +import { verifyUserIsAuthorizedToAccessPaidContent } from "./verifyUserIsAuthorizedToAccessPaidContent.js"; +import { verifyUserIsAuthorizedToPerformThisUpdate } from "./verifyUserIsAuthorizedToPerformThisUpdate.js"; + +/** + * #### AuthService + * + * This object contains methods which implement business logic related to + * authentication and authorization. + */ +export const AuthService = { + /** User authentication methods */ + authenticateUser: { + viaLoginCredentials: authenticateUserViaLoginCredentials, + viaAuthHeaderToken: AuthToken.getValidatedRequestAuthTokenPayload, + }, + /** User authorization methods */ + verifyUserIsAuthorized: { + toAccessPaidContent: verifyUserIsAuthorizedToAccessPaidContent, + toPerformThisUpdate: verifyUserIsAuthorizedToPerformThisUpdate, + }, + preFetchAndSyncUserItems, + resetPassword, + sendPasswordResetEmail, + verifyUserIsAuthorizedToPerformThisUpdate, + /** AuthToken helper function which creates+returns a new AuthToken. */ + createAuthToken: AuthToken.create, + /** GoogleOAuth2IDToken helper function which parses a GoogleOAuth2IDToken. */ + parseGoogleOAuth2IDToken: GoogleOAuth2IDToken.parse, +} as const; diff --git a/src/services/AuthService/preFetchAndSyncUserItems.ts b/src/services/AuthService/preFetchAndSyncUserItems.ts new file mode 100644 index 00000000..30408c3a --- /dev/null +++ b/src/services/AuthService/preFetchAndSyncUserItems.ts @@ -0,0 +1,54 @@ +import { User, type UserItem } from "@/models/User"; +import { UserSCAService } from "@/services/UserSCAService"; +import { UserService } from "@/services/UserService"; +import { UserSubscriptionService } from "@/services/UserSubscriptionService"; +import type { UserStripeConnectAccountItem } from "@/models/UserStripeConnectAccount"; +import type { UserSubscriptionItem } from "@/models/UserSubscription"; +import type { PreFetchedUserItems } from "@/types/open-api.js"; + +/** + * ### AuthService: preFetchAndSyncUserItems + * + * This function is used to pre-fetch User items after authenticating a user login. + * The following items are also updated/synced: + * + * - User["expoPushToken"] is updated if one is provided + * - UserStripeConnectAccount data is updated from Stripe + * - UserSubscription data is updated from Stripe + * + * @returns Up-to-date User items for the authenticated User's AuthToken payload. + */ +export const preFetchAndSyncUserItems = async ({ + authenticatedUserID, + expoPushToken, +}: { + authenticatedUserID: UserItem["id"]; + expoPushToken?: UserItem["expoPushToken"]; +}): Promise<{ + userItems: PreFetchedUserItems; + userStripeConnectAccount: UserStripeConnectAccountItem; + userSubscription: UserSubscriptionItem | null; +}> => { + // If the user has provided a new ExpoPushToken, update it in the db: + if (expoPushToken) + await User.updateItem({ id: authenticatedUserID }, { update: { expoPushToken } }); + + // Pre-fetch User items + const { userItems, userStripeConnectAccount, userSubscription } = + await UserService.queryUserItems({ authenticatedUserID }); + + // Get up-to-date StripeConnectAccount data from Stripe + const upToDateStripeConnectAccount = + await UserSCAService.refreshDataFromStripe(userStripeConnectAccount); + + // Get up-to-date Subscription data from Stripe + const upToDateSubscription = userSubscription + ? await UserSubscriptionService.refreshDataFromStripe(userSubscription) + : null; + + return { + userItems, + userStripeConnectAccount: upToDateStripeConnectAccount, + userSubscription: upToDateSubscription, + }; +}; diff --git a/src/services/AuthService/resetPassword.ts b/src/services/AuthService/resetPassword.ts new file mode 100644 index 00000000..d76ddcfb --- /dev/null +++ b/src/services/AuthService/resetPassword.ts @@ -0,0 +1,44 @@ +import { PasswordResetToken } from "@/models/PasswordResetToken"; +import { User } from "@/models/User"; +import { UserLogin } from "@/models/UserLogin"; +import { UserInputError, InternalServerError } from "@/utils/httpErrors.js"; +import type { RestApiRequestBodyByPath } from "@/types/open-api.js"; + +/** + * Validates a password-reset token and - if valid - updates the user's password. + */ +export const resetPassword = async ({ + password: newPassword, + passwordResetToken, +}: RestApiRequestBodyByPath["/auth/password-reset"]) => { + // Retrieve the password-reset token from the database + const passwordResetTokenItem = await PasswordResetToken.getItem({ + token: passwordResetToken, + }); + + // Ensure (1) the token exists and (2) it hasn't expired + if (!PasswordResetToken.isTokenValid(passwordResetTokenItem)) { + throw new UserInputError( + "Your password reset link has expired. For security reasons, please request a new password reset, or contact support if the problem persists." + ); + } + + // Validate the new password, and get an updated UserLogin object with the new pw hash + const newLocalUserLoginObj = await UserLogin.createLoginLocal(newPassword); + + // Update the User's password + const updatedUser = await User.updateItem( + { id: passwordResetTokenItem.userID }, + { + update: { login: newLocalUserLoginObj }, + ConditionExpression: "login.type = LOCAL", // Should always be the case here + } + ); + + // If `updatedUser.login.passwordHash` isn't the new pw hash, throw a 500 error + if (updatedUser.login.passwordHash !== newLocalUserLoginObj.passwordHash) { + throw new InternalServerError( + "We're sorry, but we were unable to update your password at this time. Please try again, or contact support if the problem persists." + ); + } +}; diff --git a/src/services/AuthService/sendPasswordResetEmail.ts b/src/services/AuthService/sendPasswordResetEmail.ts new file mode 100644 index 00000000..d34acaee --- /dev/null +++ b/src/services/AuthService/sendPasswordResetEmail.ts @@ -0,0 +1,44 @@ +import { pinpointClient } from "@/lib/pinpointClient"; +import { PasswordResetToken } from "@/models/PasswordResetToken"; +import { User, type UserItem } from "@/models/User"; +import { ENV } from "@/server/env"; +import { AuthError } from "@/utils/httpErrors.js"; + +/** + * ### AuthService: sendPasswordResetEmail + * + * Sends a password-reset email to the user's email address. + */ +export const sendPasswordResetEmail = async ({ email }: Pick) => { + // Ensure the user exists + const [user] = await User.query({ where: { email }, limit: 1 }); + + if (!user) throw new AuthError("User not found"); + + /* A password-reset email is only sent if the user exists and their login type is LOCAL. + Regardless of whether this is the case, the response is always the same: a 200 status code. + This is a defense-in-depth measure to prevent leaking info about a user's existence. */ + if (user.login.type === "LOCAL") { + // Make the password reset token (the model sets TTL of 15 minutes) + const { token: passwordResetToken } = await PasswordResetToken.createItem({ userID: user.id }); + + await pinpointClient.sendMessages({ + to: email, + ChannelType: "EMAIL", + TemplateConfiguration: { + EmailTemplate: { + Name: "password-reset-email", + }, + }, + MessageConfiguration: { + EmailMessage: { + Substitutions: { + passwordResetHREF: [ + `${ENV.WEB_CLIENT.URL}/reset-password?token=${passwordResetToken}`, // prettier-ignore + ], + }, + }, + }, + }); + } +}; diff --git a/src/services/AuthService/verifyUserIsAuthorizedToAccessPaidContent.test.ts b/src/services/AuthService/verifyUserIsAuthorizedToAccessPaidContent.test.ts new file mode 100644 index 00000000..7a8c0539 --- /dev/null +++ b/src/services/AuthService/verifyUserIsAuthorizedToAccessPaidContent.test.ts @@ -0,0 +1,61 @@ +import { MOCK_USERS, MOCK_USER_SCAs } from "@/tests/staticMockItems"; +import { verifyUserIsAuthorizedToAccessPaidContent } from "./verifyUserIsAuthorizedToAccessPaidContent.js"; +import type { UserSubscription } from "@/types/graphql.js"; +import type { AuthTokenPayload } from "@/types/open-api.js"; + +describe("verifyUserIsAuthorizedToAccessPaidContent()", () => { + const YEAR_2000 = new Date(2000, 0); + const YEAR_9999 = new Date(9999, 0); + + const mockUserWithSubFields = ( + subFieldsToTest: Pick + ): AuthTokenPayload => ({ + ...MOCK_USERS.USER_A, + stripeConnectAccount: MOCK_USER_SCAs.SCA_A, + subscription: { id: "", ...subFieldsToTest }, + }); + + test(`does not throw when called with a valid "active" subscription`, () => { + expect(() => { + verifyUserIsAuthorizedToAccessPaidContent({ + authenticatedUser: mockUserWithSubFields({ + status: "active", + currentPeriodEnd: YEAR_9999, + }), + }); + }).not.toThrow(); + }); + + test(`does not throw when called with a valid "trialing" subscription`, () => { + expect(() => { + verifyUserIsAuthorizedToAccessPaidContent({ + authenticatedUser: mockUserWithSubFields({ + status: "trialing", + currentPeriodEnd: YEAR_9999, + }), + }); + }).not.toThrow(); + }); + + test(`throws an error when called with a subscription with an invalid status`, () => { + expect(() => { + verifyUserIsAuthorizedToAccessPaidContent({ + authenticatedUser: mockUserWithSubFields({ + status: "past_due", + currentPeriodEnd: YEAR_9999, + }), + }); + }).toThrow("past due"); + }); + + test(`throws an error when called with an expired subscription`, () => { + expect(() => { + verifyUserIsAuthorizedToAccessPaidContent({ + authenticatedUser: mockUserWithSubFields({ + status: "active", + currentPeriodEnd: YEAR_2000, + }), + }); + }).toThrow("expired"); + }); +}); diff --git a/src/services/AuthService/verifyUserIsAuthorizedToAccessPaidContent.ts b/src/services/AuthService/verifyUserIsAuthorizedToAccessPaidContent.ts new file mode 100644 index 00000000..70dd8f08 --- /dev/null +++ b/src/services/AuthService/verifyUserIsAuthorizedToAccessPaidContent.ts @@ -0,0 +1,87 @@ +import { hasKey } from "@nerdware/ts-type-safety-utils"; +import dayjs from "dayjs"; +import { PaymentRequiredError } from "@/utils/httpErrors.js"; +import type { SubscriptionStatus } from "@/types/graphql.js"; +import type { AuthTokenPayload } from "@/types/open-api.js"; + +/** + * ### AuthService: Verify User Is Authorized To Access Paid Content + * + * Verify whether a User's existing subscription is valid for service-access. + * + * A UserSubscription is only considered valid for service-access if all of the following are true: + * + * 1. The subscription's status is `active` or `trialing`. + * 2. The subscription's `currentPeriodEnd` timestamp is in the future. + */ +export const verifyUserIsAuthorizedToAccessPaidContent = ({ + authenticatedUser, +}: { + authenticatedUser: AuthTokenPayload; +}) => { + // Destructure necessary fields + const { status, currentPeriodEnd } = authenticatedUser.subscription ?? {}; + + if ( + !status || + !hasKey(SUB_STATUS_AUTH_METADATA, status) || + SUB_STATUS_AUTH_METADATA[status].isValid !== true || + !currentPeriodEnd || + !dayjs(currentPeriodEnd).isValid() + ) { + throw new Error( + !!status && hasKey(SUB_STATUS_AUTH_METADATA, status) + ? SUB_STATUS_AUTH_METADATA[status].reason ?? "Invalid subscription." + : "Invalid subscription." + ); + } + + // Coerce to unix timestamp in seconds and compare + if (dayjs().unix() >= dayjs(currentPeriodEnd).unix()) { + throw new PaymentRequiredError( + "This subscription has expired — please update your payment settings to re-activate your subscription." + ); + } +}; + +/** + * Subscription-Status authorization metadata objects: + * + * - Subscription statuses which indicate a sub IS VALID for Fixit service usage + * contain `isValid: true`. + * - Subscription statuses which indicate a sub IS NOT VALID for Fixit service + * usage contain a `reason` plainly explaining to end users _why_ their sub is + * invalid, and what their course of action should be to resolve the issue. + * + * @see https://stripe.com/docs/api/subscriptions/object#subscription_object-status + */ +export const SUB_STATUS_AUTH_METADATA: Readonly< + Record< + SubscriptionStatus, + { isValid: true; reason?: never } | { isValid?: false; reason: string } + > +> = { + // Statuses which indicate a subscription IS VALID for service usage: + active: { + isValid: true, + }, + trialing: { + isValid: true, + }, + // Statuses which indicate a subscription IS NOT VALID for service usage: + incomplete: { + reason: "Sorry, your subscription payment is incomplete.", + }, + incomplete_expired: { + reason: "Sorry, please try again.", + }, + past_due: { + reason: "Sorry, payment for your subscription is past due. Please submit payment and try again.", // prettier-ignore + }, + canceled: { + reason: "Sorry, this subscription was canceled.", + }, + unpaid: { + reason: "Sorry, payment for your subscription is past due. Please submit payment and try again.", // prettier-ignore + }, +}; diff --git a/src/graphql/_helpers/verifyUserIsAuthorizedToPerformThisUpdate.ts b/src/services/AuthService/verifyUserIsAuthorizedToPerformThisUpdate.ts similarity index 62% rename from src/graphql/_helpers/verifyUserIsAuthorizedToPerformThisUpdate.ts rename to src/services/AuthService/verifyUserIsAuthorizedToPerformThisUpdate.ts index 2f1f4a7c..de32324b 100644 --- a/src/graphql/_helpers/verifyUserIsAuthorizedToPerformThisUpdate.ts +++ b/src/services/AuthService/verifyUserIsAuthorizedToPerformThisUpdate.ts @@ -1,45 +1,40 @@ import { hasKey } from "@nerdware/ts-type-safety-utils"; -import { GqlUserInputError, GqlForbiddenError } from "@/utils/httpErrors.js"; +import { ForbiddenError } from "@/utils/httpErrors.js"; /** * This function performs the following common authorization validation checks for * update-mutations involving an item with a `status` field (e.g., `WorkOrder` and * `Invoice`): * - * 1. Ensures the item exists - * 2. Ensures the authenticated user is allowed to perform the update - * 3. Ensures the item's existing `status` does not forbid the update + * 1. Ensures the authenticated user is allowed to perform the update + * 2. Ensures the item's existing `status` does not forbid the update */ export function verifyUserIsAuthorizedToPerformThisUpdate< ItemWithStatus extends { status: string }, >( - item: ItemWithStatus | undefined, + item: ItemWithStatus, { - itemNotFoundErrorMessage, idOfUserWhoCanPerformThisUpdate: allowedUserID, authenticatedUserID: userID, forbiddenStatuses, - }: GqlValidationParams -): asserts item is NonNullable { - // Ensure the item exists - if (!item) { - throw new GqlUserInputError(itemNotFoundErrorMessage); - } + }: VeritfyUserIsAuthorizedParams +) { // Ensure the authenticated user is allowed to perform the update if (allowedUserID !== userID) { - throw new GqlForbiddenError("Access denied."); + throw new ForbiddenError("Access denied."); } // Ensure the item's existing `status` does not forbid the update if (forbiddenStatuses && hasKey(forbiddenStatuses, item.status)) { - throw new GqlForbiddenError(forbiddenStatuses[item.status as keyof typeof forbiddenStatuses]); + throw new ForbiddenError(forbiddenStatuses[item.status as keyof typeof forbiddenStatuses]); } } /** * Validation parameters for controlling the behavior of {@link verifyUserCanPerformThisUpdate}. */ -export type GqlValidationParams = { - itemNotFoundErrorMessage: string; +export type VeritfyUserIsAuthorizedParams< + ItemWithStatus extends { status: string } = { status: string }, +> = { idOfUserWhoCanPerformThisUpdate: string | null | undefined; authenticatedUserID: string; forbiddenStatuses?: ForbiddenStatusErrorMessages; diff --git a/src/services/CheckoutService/checkPromoCode.ts b/src/services/CheckoutService/checkPromoCode.ts new file mode 100644 index 00000000..3a7e510d --- /dev/null +++ b/src/services/CheckoutService/checkPromoCode.ts @@ -0,0 +1,19 @@ +import { promoCodesCache } from "@/lib/cache/promoCodesCache.js"; +import type { PromoCodeInfo } from "@/types/open-api.js"; + +/** + * This checks the provided `promoCode`s validity and discount percentage (if valid/applicable). + */ +export const checkPromoCode = ({ + promoCode: promoCodeValueToCheck, +}: { + promoCode: string; +}): PromoCodeInfo => { + const maybeDiscountPercentage = promoCodesCache.get(promoCodeValueToCheck)?.discount; + + return { + value: promoCodeValueToCheck, + isValidPromoCode: !!maybeDiscountPercentage, + ...(!!maybeDiscountPercentage && { discountPercentage: maybeDiscountPercentage }), + }; +}; diff --git a/src/services/CheckoutService/index.ts b/src/services/CheckoutService/index.ts new file mode 100644 index 00000000..95c1a419 --- /dev/null +++ b/src/services/CheckoutService/index.ts @@ -0,0 +1,13 @@ +import { checkPromoCode } from "./checkPromoCode.js"; +import { processCheckoutPayment } from "./processCheckoutPayment.js"; + +/** + * #### CheckoutService + * + * This object contains methods which implement business logic related to + * the checkout process. + */ +export const CheckoutService = { + checkPromoCode, + processPayment: processCheckoutPayment, +} as const; diff --git a/src/services/CheckoutService/processCheckoutPayment.ts b/src/services/CheckoutService/processCheckoutPayment.ts new file mode 100644 index 00000000..52a726b4 --- /dev/null +++ b/src/services/CheckoutService/processCheckoutPayment.ts @@ -0,0 +1,206 @@ +import { isString, getTypeSafeError } from "@nerdware/ts-type-safety-utils"; +import { eventEmitter } from "@/events/eventEmitter.js"; +import { stripe } from "@/lib/stripe/stripeClient.js"; +import { SUBSCRIPTION_PRICE_NAMES as SUB_PRICE_NAMES } from "@/models/UserSubscription/enumConstants.js"; +import { UserSubscriptionService } from "@/services/UserSubscriptionService"; +import { PaymentRequiredError } from "@/utils/httpErrors.js"; +import { logger } from "@/utils/logger.js"; +import type { + StripeSubscriptionWithClientSecret, + StripeCustomerWithClientSecret, +} from "@/lib/stripe/types.js"; +import type { UserSubscriptionItem } from "@/models/UserSubscription"; +import type { CreateSubscriptionParams } from "@/services/UserSubscriptionService/createSubscription.js"; +import type { CheckoutCompletionInfo, AuthTokenPayload } from "@/types/open-api.js"; +import type Stripe from "stripe"; +import type { Simplify, OverrideProperties, SetOptional } from "type-fest"; + +/** + * `UserSubscriptionService`.{@link processCheckoutPayment} params + */ +export type ProcessCheckoutPaymentParams = Simplify< + { + paymentMethodID: string; + request: { ip: string; userAgent: string }; + } & OverrideProperties +>; + +export type CheckoutFlowSubscriptionFields = SetOptional< + UserSubscriptionItem, + "sk" | "createdAt" | "updatedAt" +>; + +/** + * This function processes checkout payments. If the supplied payment details result + * in a successful payment, or if additional user input is required to confirm the + * payment (e.g., for [3D-Secure/SCA][3ds-info]), this function returns an object + * containing {@link CheckoutCompletionInfo}, as well as `subscription` data. If a + * non-zero amount is owed (TRIAL/PROMO_CODE) and the payment-intent fails, the + * function throws a 402 Payment Required error. + * + * The Stripe API is used to attach the provided payment function to the user/customer + * and set it as their default payment function. The User may already have one ore more + * subscriptions (e.g., if they previously created a subscription but didn't complete + * the payment process for it), in which case the array of subscriptions returned from + * Stripe is searched for a subscription that _**is not expired**_. + * + * If a non-expired subscription _**is not**_ found, one is upserted into the db. + * Note that `upsert` is used because a sub may already exist in the db, but it may be + * _**expired**_, in which case it's desirable to simply overwrite it so the db isn't + * populated with dangling sub items that will never be used. + * + * Once a valid, non-expired subscription has been obtained (either from Stripe or the + * `upsert` operation), the `status` of the `payment_intent` on the sub's latest invoice + * is checked — any value other than "succeeded" results in a 402 Payment Required error. + * If the status is "succeeded", the checkout/payment succeeded. + * + * [3ds-info]: https://stripe.com/docs/payments/3d-secure + */ +export const processCheckoutPayment = async ( + { + user, + paymentMethodID, + selectedSubscription, + promoCode, + request + }: ProcessCheckoutPaymentParams // prettier-ignore +): Promise<{ + checkoutCompletionInfo: NonNullable; + subscription: CheckoutFlowSubscriptionFields; +}> => { + // Vars to hold the returned values: + let checkoutCompletionInfo: NonNullable; + let subscription: CheckoutFlowSubscriptionFields; + // Var to hold intermediary values: + let latestInvoice: StripeSubscriptionWithClientSecret["latest_invoice"]; + let amountPaid: number; + + try { + // Attach the payment method to the customer + await stripe.paymentMethods.attach(paymentMethodID, { customer: user.stripeCustomerID }); + + // Change the default invoice settings on the customer to the new payment method + const { subscriptions } = (await stripe.customers.update(user.stripeCustomerID, { + invoice_settings: { default_payment_method: paymentMethodID }, + expand: ["subscriptions.data.latest_invoice.payment_intent"], + })) as Stripe.Response; + + let nonExpiredSubscription: StripeSubscriptionWithClientSecret | undefined; + + // See if there's an existing non-expired subscription + if (subscriptions.data.length >= 1) { + nonExpiredSubscription = subscriptions.data.find( + (sub) => sub.id.startsWith("sub") && sub.status !== "incomplete_expired" + ); + } + + // If the User already has a valid sub, obtain the values we need from that one: + // prettier-ignore + if (nonExpiredSubscription) { + // Normalize and destruct the nonExpiredSubscription object + const { latest_invoice, id, currentPeriodEnd, status, productID, priceID, createdAt } = + UserSubscriptionService.normalizeStripeFields(nonExpiredSubscription); + + // Update the subscription and latestInvoice objects + subscription = { userID: user.id, id, currentPeriodEnd, status, productID, priceID, createdAt }; + latestInvoice = latest_invoice; + } else { + // If the User does not have a valid sub, create one: + const { userSubscription, stripeSubscriptionObject } = + await UserSubscriptionService.createSubscription({ user, selectedSubscription, promoCode }); + + // Update the subscription and latestInvoice objects + subscription = userSubscription; + latestInvoice = stripeSubscriptionObject.latest_invoice; + } + + /* HANDLE TRIAL/PROMO_CODE + The `latest_invoice` will not have a `payment_intent.id` in checkout situations + which don't involve an immediate payment — i.e., if the user selected a TRIAL, + or provided a VIP `promoCode` which grants them 100% off at checkout. */ + if (!latestInvoice.payment_intent?.id) { + /* Just to be sure the sub/payment are in the expected state, assertions are + made regarding the expected TRIAL/PROMO_CODE. If neither conditions apply, + Stripe should have provided `payment_intent.id`, so an error is thrown. */ + const isTrialSub = + selectedSubscription === SUB_PRICE_NAMES.TRIAL && subscription.status === "trialing"; + const wasVipPromoCodeApplied = + !!promoCode && latestInvoice.discount?.coupon.percent_off === 100; + + if (!isTrialSub && !wasVipPromoCodeApplied) + throw new Error("Stripe Error: Failed to retrieve payment details"); + + // Update checkoutCompletionInfo: + checkoutCompletionInfo = { + isCheckoutComplete: latestInvoice.paid === true, + // Note: for TRIAL/PROMO_CODE subs, `latest_invoice.paid` should be `true` here + }; + + // Set `amountPaid` to the amount received by the payment intent + amountPaid = latestInvoice.payment_intent?.amount_received ?? 0; + } else { + // Confirm intent with collected payment method + const { + status: paymentStatus, + client_secret: clientSecret, + invoice, + amount_received, + } = (await stripe.paymentIntents.confirm(latestInvoice.payment_intent.id, { + payment_method: paymentMethodID, + mandate_data: { + customer_acceptance: { + type: "online", + online: { + ip_address: request.ip, + user_agent: request.userAgent, + }, + }, + }, + expand: ["invoice"], // expand to get `invoice.paid` + })) as Stripe.Response>; + + // Sanity-check: ensure the paymentStatus and clientSecret are strings + if (!isString(paymentStatus) || !isString(clientSecret)) + throw new Error("Stripe Error: payment confirmation failed."); + + const isCheckoutComplete = + ["succeeded", "requires_action"].includes(paymentStatus) && invoice.paid === true; + + if (!isCheckoutComplete) throw new Error("Your payment was declined."); + + // Update checkoutCompletionInfo: + checkoutCompletionInfo = { + isCheckoutComplete, + ...(clientSecret && { clientSecret }), + }; + + // Update the amountPaid value: + amountPaid = amount_received; + } + + // Check the sub's status + const { status: subStatusAfterPayment } = await stripe.subscriptions.retrieve(subscription.id); + + // Update the `CheckoutFlowSubscriptionFields` object with the new sub details: + subscription.status = subStatusAfterPayment; + + // If an error occurs, ensure the 402 status code is provided. + } catch (err: unknown) { + const error = getTypeSafeError(err); + logger.stripe(error, "processCheckoutPayment"); + throw new PaymentRequiredError(error.message); + } + + // Emit the CheckoutCompleted event with payment details: + eventEmitter.emitCheckoutCompleted({ + user, + priceName: selectedSubscription, + paymentIntentID: latestInvoice.payment_intent?.id, + amountPaid, + }); + + return { + checkoutCompletionInfo, + subscription, + }; +}; diff --git a/src/services/ContactService/createContact.ts b/src/services/ContactService/createContact.ts new file mode 100644 index 00000000..1469ccfc --- /dev/null +++ b/src/services/ContactService/createContact.ts @@ -0,0 +1,37 @@ +import { Contact, type ContactItem } from "@/models/Contact"; +import { User } from "@/models/User"; +import { UserInputError } from "@/utils/httpErrors.js"; +import type { Contact as GqlContactObject } from "@/types/graphql.js"; + +/** + * ### ContactService: createContact + */ +export const createContact = async ({ + authenticatedUserID, + contactUserID, +}: { + authenticatedUserID: string; + contactUserID: string; +}): Promise => { + // First, ensure the user hasn't somehow sent their own ID + if (contactUserID.toUpperCase() === authenticatedUserID.toUpperCase()) + throw new UserInputError("Can not add yourself as a contact"); + + const requestedContactUser = await User.getItem({ id: contactUserID }); + + if (!requestedContactUser) throw new UserInputError("Requested user not found."); + + // Note: createItem won't overwrite existing if Contact already exists. + const newContact = await Contact.createItem({ + userID: authenticatedUserID, + contactUserID: requestedContactUser.id, + handle: requestedContactUser.handle, + }); + + return { + ...newContact, + email: requestedContactUser.email, + phone: requestedContactUser.phone, + profile: requestedContactUser.profile, + }; +}; diff --git a/src/services/ContactService/findContactByID.ts b/src/services/ContactService/findContactByID.ts new file mode 100644 index 00000000..ca3c38aa --- /dev/null +++ b/src/services/ContactService/findContactByID.ts @@ -0,0 +1,22 @@ +import { Contact } from "@/models/Contact"; +import { UserInputError } from "@/utils/httpErrors.js"; + +/** + * ### ContactService: findContactByID + */ +export const findContactByID = async ({ + authenticatedUserID, + contactID, +}: { + authenticatedUserID: string; + contactID: string; +}) => { + const contact = await Contact.getItem({ + userID: authenticatedUserID, + id: contactID, + }); + + if (!contact) throw new UserInputError("A contact with the provided ID could not be found."); + + return contact; +}; diff --git a/src/services/ContactService/index.ts b/src/services/ContactService/index.ts new file mode 100644 index 00000000..efd3c6a5 --- /dev/null +++ b/src/services/ContactService/index.ts @@ -0,0 +1,13 @@ +import { createContact } from "./createContact.js"; +import { findContactByID } from "./findContactByID.js"; + +/** + * #### ContactService + * + * This object contains methods which implement business logic for operations + * related to users' Contacts. + */ +export const ContactService = { + createContact, + findContactByID, +} as const; diff --git a/src/services/InvoiceService/createInvoice.ts b/src/services/InvoiceService/createInvoice.ts new file mode 100644 index 00000000..2eb91d83 --- /dev/null +++ b/src/services/InvoiceService/createInvoice.ts @@ -0,0 +1,22 @@ +import { eventEmitter } from "@/events/eventEmitter.js"; +import { Invoice } from "@/models/Invoice"; +import type { InvoiceInput } from "@/types/graphql.js"; + +/** + * ### InvoiceService - createInvoice + */ +export const createInvoice = async (invInput: { createdByUserID: string } & InvoiceInput) => { + const createdInvoice = await Invoice.createItem({ + createdByUserID: invInput.createdByUserID, + assignedToUserID: invInput.assignedTo, + amount: invInput.amount, + status: "OPEN", + ...(!!invInput.workOrderID && { + workOrderID: invInput.workOrderID, + }), + }); + + eventEmitter.emitInvoiceCreated(createdInvoice); + + return createdInvoice; +}; diff --git a/src/services/InvoiceService/deleteInvoice.ts b/src/services/InvoiceService/deleteInvoice.ts new file mode 100644 index 00000000..a5cf89eb --- /dev/null +++ b/src/services/InvoiceService/deleteInvoice.ts @@ -0,0 +1,41 @@ +import { eventEmitter } from "@/events/eventEmitter.js"; +import { Invoice } from "@/models/Invoice"; +import { AuthService } from "@/services/AuthService"; +import { UserInputError } from "@/utils/httpErrors.js"; +import type { AuthTokenPayload } from "@/types/open-api.js"; + +/** + * ### InvoiceService - deleteInvoice + */ +export const deleteInvoice = async ({ + invoiceID, + authenticatedUser, +}: { + invoiceID: string; + authenticatedUser: AuthTokenPayload; +}) => { + const [existingInv] = await Invoice.query({ + where: { id: invoiceID }, + limit: 1, + }); + + if (!existingInv) throw new UserInputError("An invoice with the provided ID could not be found."); + + AuthService.verifyUserIsAuthorized.toPerformThisUpdate(existingInv, { + idOfUserWhoCanPerformThisUpdate: existingInv.createdByUserID, + authenticatedUserID: authenticatedUser.id, + forbiddenStatuses: { + CLOSED: "The requested invoice has already been closed.", + DISPUTED: "The requested invoice has been disputed and cannot be deleted at this time.", + }, + }); + + const deletedInvoice = await Invoice.deleteItem({ + createdByUserID: existingInv.createdByUserID, + id: existingInv.id, + }); + + eventEmitter.emitInvoiceDeleted(deletedInvoice); + + return deletedInvoice; +}; diff --git a/src/services/InvoiceService/findInvoiceByID.ts b/src/services/InvoiceService/findInvoiceByID.ts new file mode 100644 index 00000000..cbdf5cbc --- /dev/null +++ b/src/services/InvoiceService/findInvoiceByID.ts @@ -0,0 +1,16 @@ +import { Invoice } from "@/models/Invoice"; +import { UserInputError } from "@/utils/httpErrors.js"; + +/** + * ### InvoiceService: findInvoiceByID + */ +export const findInvoiceByID = async ({ invoiceID }: { invoiceID: string }) => { + const [existingInv] = await Invoice.query({ + where: { id: invoiceID }, + limit: 1, + }); + + if (!existingInv) throw new UserInputError("An invoice with the provided ID could not be found."); + + return existingInv; +}; diff --git a/src/services/InvoiceService/index.ts b/src/services/InvoiceService/index.ts new file mode 100644 index 00000000..2f0758b2 --- /dev/null +++ b/src/services/InvoiceService/index.ts @@ -0,0 +1,20 @@ +import { createInvoice } from "./createInvoice.js"; +import { deleteInvoice } from "./deleteInvoice.js"; +import { findInvoiceByID } from "./findInvoiceByID.js"; +import { payInvoice } from "./payInvoice.js"; +import { queryUsersInvoices } from "./queryUsersInvoices.js"; +import { updateInvoiceAmount } from "./updateInvoiceAmount.js"; + +/** + * #### InvoiceService + * + * This object contains methods which implement business logic for Invoice operations. + */ +export const InvoiceService = { + createInvoice, + deleteInvoice, + findInvoiceByID, + payInvoice, + queryUsersInvoices, + updateInvoiceAmount, +} as const; diff --git a/src/services/InvoiceService/payInvoice.ts b/src/services/InvoiceService/payInvoice.ts new file mode 100644 index 00000000..af0a6edc --- /dev/null +++ b/src/services/InvoiceService/payInvoice.ts @@ -0,0 +1,69 @@ +import { eventEmitter } from "@/events/eventEmitter.js"; +import { stripe } from "@/lib/stripe/stripeClient.js"; +import { Invoice } from "@/models/Invoice"; +import { AuthService } from "@/services/AuthService"; +import { UserInputError, ForbiddenError } from "@/utils/httpErrors.js"; +import type { AuthTokenPayload } from "@/types/open-api.js"; + +/** + * ### InvoiceService - payInvoice + */ +export const payInvoice = async ({ + invoiceID, + authenticatedUser, +}: { + invoiceID: string; + authenticatedUser: AuthTokenPayload; +}) => { + // Fetch the Invoice + const [existingInv] = await Invoice.query({ where: { id: invoiceID }, limit: 1 }); + + if (!existingInv) throw new UserInputError("An invoice with the provided ID could not be found."); + + AuthService.verifyUserIsAuthorized.toPerformThisUpdate(existingInv, { + idOfUserWhoCanPerformThisUpdate: existingInv.assignedToUserID, + authenticatedUserID: authenticatedUser.id, + forbiddenStatuses: { + CLOSED: "The requested invoice has already been closed.", + DISPUTED: "The requested invoice has been disputed and cannot be paid at this time.", + }, + }); + + // Fetch the Stripe Customer + const stripeCustomer = await stripe.customers.retrieve(authenticatedUser.stripeCustomerID); + + // Ensure the user hasn't removed themselves as a Stripe Customer + if (stripeCustomer.deleted) { + throw new ForbiddenError( + "Invalid Stripe Customer - please review your payment settings in your Stripe Dashboard and try again." + ); + } + + const paymentIntent = await stripe.paymentIntents.create({ + customer: authenticatedUser.stripeCustomerID, + payment_method: stripeCustomer.invoice_settings.default_payment_method as string, // Cast from Stripe.StripePaymentMethod, which is only an object when expanded. + amount: existingInv.amount, + currency: "usd", // <-- Note: would need to be paramaterized for i18n + confirm: true, + on_behalf_of: authenticatedUser.stripeConnectAccount.id, + transfer_data: { + destination: authenticatedUser.stripeConnectAccount.id, + }, + }); + + const wasInvoiceSuccessfullyPaid = paymentIntent.status === "succeeded"; + + const updatedInvoice = await Invoice.updateItem( + { id: existingInv.id, createdByUserID: existingInv.createdByUserID }, + { + update: { + stripePaymentIntentID: paymentIntent.id, + ...(wasInvoiceSuccessfullyPaid && { status: "CLOSED" }), + }, + } + ); + + if (wasInvoiceSuccessfullyPaid) eventEmitter.emitInvoicePaid(updatedInvoice); + + return updatedInvoice; +}; diff --git a/src/services/InvoiceService/queryUsersInvoices.ts b/src/services/InvoiceService/queryUsersInvoices.ts new file mode 100644 index 00000000..34b748ff --- /dev/null +++ b/src/services/InvoiceService/queryUsersInvoices.ts @@ -0,0 +1,28 @@ +import { Invoice } from "@/models/Invoice"; +import type { AuthTokenPayload } from "@/types/open-api.js"; + +/** + * ### InvoiceService: queryUsersInvoices + */ +export const queryUsersInvoices = async ({ + authenticatedUser, +}: { + authenticatedUser: AuthTokenPayload; +}) => { + return { + // Query for all Invoices created by the authenticated User + createdByUser: await Invoice.query({ + where: { + createdByUserID: authenticatedUser.id, + id: { beginsWith: Invoice.SK_PREFIX }, + }, + }), + // Query for all Invoices assigned to the authenticated User + assignedToUser: await Invoice.query({ + where: { + assignedToUserID: authenticatedUser.id, + id: { beginsWith: Invoice.SK_PREFIX }, + }, + }), + }; +}; diff --git a/src/services/InvoiceService/updateInvoiceAmount.ts b/src/services/InvoiceService/updateInvoiceAmount.ts new file mode 100644 index 00000000..0c8e1c9b --- /dev/null +++ b/src/services/InvoiceService/updateInvoiceAmount.ts @@ -0,0 +1,41 @@ +import { eventEmitter } from "@/events/eventEmitter.js"; +import { Invoice } from "@/models/Invoice"; +import { AuthService } from "@/services/AuthService"; +import { UserInputError } from "@/utils/httpErrors.js"; +import type { MutationUpdateInvoiceAmountArgs } from "@/types/graphql.js"; +import type { AuthTokenPayload } from "@/types/open-api.js"; + +/** + * ### InvoiceService - updateInvoiceAmount + */ +export const updateInvoiceAmount = async ({ + invoiceID, + amount, + authenticatedUser, +}: MutationUpdateInvoiceAmountArgs & { authenticatedUser: AuthTokenPayload }) => { + const [existingInv] = await Invoice.query({ where: { id: invoiceID }, limit: 1 }); + + if (!existingInv) throw new UserInputError("An invoice with the provided ID could not be found."); + + AuthService.verifyUserIsAuthorized.toPerformThisUpdate(existingInv, { + idOfUserWhoCanPerformThisUpdate: existingInv.createdByUserID, + authenticatedUserID: authenticatedUser.id, + forbiddenStatuses: { + CLOSED: "The requested invoice has already been closed.", + DISPUTED: + "The requested invoice has been disputed and cannot be updated at this time. " + + "Please contact the invoice's recipient for details and further assistance.", + }, + }); + + const updatedInvoice = await Invoice.updateItem( + { id: existingInv.id, createdByUserID: existingInv.createdByUserID }, + { + update: { amount }, + } + ); + + eventEmitter.emitInvoiceUpdated(updatedInvoice); + + return updatedInvoice; +}; diff --git a/src/services/README.md b/src/services/README.md new file mode 100644 index 00000000..37a10bb8 --- /dev/null +++ b/src/services/README.md @@ -0,0 +1,11 @@ +# Services + +This directory contains the application's `services`. + +### Service Design Goals + +Each `service` is designed to achieve the following design goals: + +- Serve as the primary location for business logic. +- Be _protocol agnostic_, i.e., be capable of use in both REST and GraphQL contexts (no request/response handling). +- Provide single points of entry for logical use cases which involve multiple [model](../models/README.md)-method invocations. diff --git a/src/services/UserSCAService/UserSCAService.test.ts b/src/services/UserSCAService/UserSCAService.test.ts new file mode 100644 index 00000000..c32c1715 --- /dev/null +++ b/src/services/UserSCAService/UserSCAService.test.ts @@ -0,0 +1,34 @@ +import { isValidStripeID } from "@/lib/stripe/helpers.js"; +import { userModelHelpers } from "@/models/User/helpers.js"; +import { scaModelHelpers } from "@/models/UserStripeConnectAccount/helpers.js"; +import { MOCK_USER_SCAs, MOCK_USERS } from "@/tests/staticMockItems"; +import { UserSCAService } from "./index.js"; + +describe("UserSCAService", () => { + describe("UserSCAService.registerNewUserSCA()", () => { + test("returns a valid User when called with valid arguments", async () => { + // Arrange the inputs + const input = { + ...MOCK_USER_SCAs.SCA_A, + email: MOCK_USERS.USER_A.email, + phone: MOCK_USERS.USER_A.phone, + profile: MOCK_USERS.USER_A.profile, + }; + + // Act on the UserStripeConnectAccount Model's createItem method + const result = await UserSCAService.registerNewUserSCA(input); + + // Assert the result + expect(result).toStrictEqual({ + userID: expect.toSatisfyFn((value) => userModelHelpers.id.isValid(value)), + id: expect.toSatisfyFn((value) => isValidStripeID.connectAccount(value)), + sk: expect.toSatisfyFn((value) => scaModelHelpers.sk.isValid(value)), + detailsSubmitted: expect.any(Boolean), + chargesEnabled: expect.any(Boolean), + payoutsEnabled: expect.any(Boolean), + createdAt: expect.any(Date), + updatedAt: expect.any(Date), + }); + }); + }); +}); diff --git a/src/services/UserSCAService/index.ts b/src/services/UserSCAService/index.ts new file mode 100644 index 00000000..dd49a50c --- /dev/null +++ b/src/services/UserSCAService/index.ts @@ -0,0 +1,13 @@ +import { refreshDataFromStripe } from "./refreshDataFromStripe.js"; +import { registerNewUserSCA } from "./registerNewUserSCA.js"; + +/** + * #### UserSCAService + * + * This object contains methods which implement business logic for operations + * related to User Stripe Connect Accounts. + */ +export const UserSCAService = { + refreshDataFromStripe, + registerNewUserSCA, +} as const; diff --git a/src/services/UserSCAService/refreshDataFromStripe.ts b/src/services/UserSCAService/refreshDataFromStripe.ts new file mode 100644 index 00000000..ecc1f910 --- /dev/null +++ b/src/services/UserSCAService/refreshDataFromStripe.ts @@ -0,0 +1,48 @@ +import { stripe } from "@/lib/stripe/stripeClient.js"; +import { + UserStripeConnectAccount, + type UserStripeConnectAccountItem, +} from "@/models/UserStripeConnectAccount"; + +/** + * Fetches up-to-date Stripe Connect Account data from Stripe and updates the DB if necessary. + * + * - `UserStripeConnectAccountItem` fields kept in sync with Stripe: + * - `detailsSubmitted` + * - `chargesEnabled` + * - `payoutsEnabled` + * + * - `UserStripeConnectAccountItem` fields needed to update the db, if necessary: + * - `id` (the Stripe-provided SCA ID) + * - `userID` + * + * @returns Up-to-date UserStripeConnectAccount fields. + */ +export const refreshDataFromStripe = async ( + existingSCA: UserStripeConnectAccountItem +): Promise => { + // Get up-to-date values from Stripe + const stripeSCA = await stripe.accounts.retrieve(existingSCA.id); + + // Update DB if existing values are stale + if ( + stripeSCA.details_submitted !== existingSCA.detailsSubmitted || + stripeSCA.charges_enabled !== existingSCA.chargesEnabled || + stripeSCA.payouts_enabled !== existingSCA.payoutsEnabled + ) { + // Return the updated SCA + return await UserStripeConnectAccount.updateItem( + { userID: existingSCA.userID }, + { + update: { + detailsSubmitted: stripeSCA.details_submitted, + chargesEnabled: stripeSCA.charges_enabled, + payoutsEnabled: stripeSCA.payouts_enabled, + }, + } + ); + } + + // If no update is required, return the existing SCA fields + return existingSCA; +}; diff --git a/src/models/UserStripeConnectAccount/createOne.ts b/src/services/UserSCAService/registerNewUserSCA.ts similarity index 56% rename from src/models/UserStripeConnectAccount/createOne.ts rename to src/services/UserSCAService/registerNewUserSCA.ts index 3401c956..d6570677 100644 --- a/src/models/UserStripeConnectAccount/createOne.ts +++ b/src/services/UserSCAService/registerNewUserSCA.ts @@ -1,28 +1,28 @@ import { stripe } from "@/lib/stripe/stripeClient.js"; import { UserStripeConnectAccount, - type UserStripeConnectAccountItem, -} from "@/models/UserStripeConnectAccount/UserStripeConnectAccount.js"; -import type { UserItem } from "@/models/User/User.js"; + type UserStripeConnectAccountCreateItemParams as UserSCACreateItemParams, +} from "@/models/UserStripeConnectAccount"; +import type { UserItem } from "@/models/User"; +import type { Simplify } from "type-fest"; /** - * This method creates a `UserStripeConnectAccount` item in both the Fixit database - * and Stripe's API (via `stripe.accounts.create`). + * `UserSCAService`.{@link registerNewUserSCA} params */ -export const createOne = async function ( - this: typeof UserStripeConnectAccount, - { - userID, - email, - phone, - profile, - }: { - userID: UserItem["id"]; - email: UserItem["email"]; - phone: UserItem["phone"]; - profile?: UserItem["profile"]; - } -): Promise> { +export type RegisterNewUserSCAParams = Simplify< + Pick & Pick +>; + +/** + * This method creates a `UserStripeConnectAccount` item in both the Fixit + * database and Stripe's API via `stripe.accounts.create`. + */ +export const registerNewUserSCA = async ({ + userID, + email, + phone, + profile, +}: RegisterNewUserSCAParams) => { // Create Stripe Connect Account via Stripe API const { id: stripeConnectAccountID, @@ -41,18 +41,18 @@ export const createOne = async function ( business_type: "individual", company: { ...(phone && { phone }), - ...(profile?.businessName && { name: profile.businessName }), + ...(profile.businessName && { name: profile.businessName }), }, individual: { email, ...(phone && { phone }), - ...(profile?.givenName && { first_name: profile.givenName }), - ...(profile?.familyName && { last_name: profile.familyName }), + ...(profile.givenName && { first_name: profile.givenName }), + ...(profile.familyName && { last_name: profile.familyName }), }, business_profile: { support_email: email, ...(phone && { support_phone: phone }), - ...(profile?.businessName && { name: profile.businessName }), + ...(profile.businessName && { name: profile.businessName }), }, tos_acceptance: { service_agreement: "full", @@ -65,7 +65,7 @@ export const createOne = async function ( }); // Create UserStripeConnectAccount in DynamoDB - return await this.createItem({ + return await UserStripeConnectAccount.createItem({ userID, id: stripeConnectAccountID, detailsSubmitted, diff --git a/src/services/UserService/UserService.test.ts b/src/services/UserService/UserService.test.ts new file mode 100644 index 00000000..257c59a6 --- /dev/null +++ b/src/services/UserService/UserService.test.ts @@ -0,0 +1,60 @@ +import { isValidStripeID } from "@/lib/stripe/helpers.js"; +import { userModelHelpers } from "@/models/User/helpers.js"; +import { scaModelHelpers } from "@/models/UserStripeConnectAccount/helpers.js"; +import { MOCK_USERS } from "@/tests/staticMockItems/users.js"; +import { UserService } from "./index.js"; + +describe("UserService", () => { + describe("UserService.registerNewUser()", () => { + test("returns a valid User when called with valid arguments", async () => { + // Arrange mock Users + for (const key in MOCK_USERS) { + // Get input for UserService.registerNewUser() method + const mockUser = MOCK_USERS[key as keyof typeof MOCK_USERS]; + const input = { + ...mockUser, + ...(mockUser.login.type === "LOCAL" + ? { password: "MockPassword@123" } + : { googleID: mockUser.login.googleID }), + }; + + // Act on the UserService.registerNewUser() method + const result = await UserService.registerNewUser(input); + + // Assert the result + expect(result).toStrictEqual({ + id: expect.toSatisfyFn((value) => userModelHelpers.id.isValid(value)), + sk: expect.toSatisfyFn((value) => userModelHelpers.sk.isValid(value)), + handle: mockUser.handle, + email: mockUser.email, + phone: mockUser.phone, + stripeCustomerID: mockUser.stripeCustomerID, + ...(mockUser.expoPushToken && { expoPushToken: mockUser.expoPushToken }), + profile: { + ...mockUser.profile, + givenName: expect.toBeOneOf([undefined, null, expect.any(String)]), + familyName: expect.toBeOneOf([undefined, null, expect.any(String)]), + businessName: expect.toBeOneOf([undefined, null, expect.any(String)]), + photoUrl: expect.toBeOneOf([undefined, null, expect.any(String)]), + }, + login: { + ...mockUser.login, + ...(mockUser.login.type === "LOCAL" && { passwordHash: expect.any(String) }), + }, + stripeConnectAccount: { + userID: expect.toSatisfyFn((value) => userModelHelpers.id.isValid(value)), + id: expect.toSatisfyFn((value) => isValidStripeID.connectAccount(value)), + sk: expect.toSatisfyFn((value) => scaModelHelpers.sk.isValid(value)), + detailsSubmitted: expect.any(Boolean), + chargesEnabled: expect.any(Boolean), + payoutsEnabled: expect.any(Boolean), + createdAt: expect.any(Date), + updatedAt: expect.any(Date), + }, + createdAt: expect.any(Date), + updatedAt: expect.any(Date), + }); + } + }); + }); +}); diff --git a/src/services/UserService/getUserByHandle.ts b/src/services/UserService/getUserByHandle.ts new file mode 100644 index 00000000..ca366aa7 --- /dev/null +++ b/src/services/UserService/getUserByHandle.ts @@ -0,0 +1,26 @@ +import { usersCache } from "@/lib/cache/usersCache.js"; +import { User, userModelHelpers } from "@/models/User"; +import type { User as GqlUser } from "@/types/graphql.js"; + +/** + * ### UserService: getUserByHandle + */ +export const getUserByHandle = async ({ handle }: { handle: string }): Promise => { + // Check usersCache + let publicUser: GqlUser | undefined = usersCache.get(handle); + + // If neither of the above methods yielded data for publicUser, get it from the DB as a last resort. + if (!publicUser) { + publicUser = await User.getItem({ + id: userModelHelpers.id.format(handle), + }); + + // If we found a user, cache it for future use. + if (publicUser) usersCache.set(publicUser.handle, publicUser); + } + + // If for some reason there's still no data for publicUser, throw an error. + if (!publicUser) throw new Error("User not found."); + + return publicUser; +}; diff --git a/src/services/UserService/index.ts b/src/services/UserService/index.ts new file mode 100644 index 00000000..1489bf7d --- /dev/null +++ b/src/services/UserService/index.ts @@ -0,0 +1,14 @@ +import { getUserByHandle } from "./getUserByHandle.js"; +import { queryUserItems } from "./queryUserItems.js"; +import { registerNewUser } from "./registerNewUser.js"; + +/** + * #### UserService + * + * This object contains methods which implement business logic for User operations. + */ +export const UserService = { + getUserByHandle, + queryUserItems, + registerNewUser, +} as const; diff --git a/src/services/UserService/queryUserItems.ts b/src/services/UserService/queryUserItems.ts new file mode 100644 index 00000000..1a7dd60f --- /dev/null +++ b/src/services/UserService/queryUserItems.ts @@ -0,0 +1,216 @@ +import { safeJsonStringify } from "@nerdware/ts-type-safety-utils"; +import { usersCache } from "@/lib/cache/usersCache.js"; +import { Contact, type ContactItem } from "@/models/Contact"; +import { Invoice, type InvoiceItem } from "@/models/Invoice"; +import { USER_SK_PREFIX_STR } from "@/models/User"; +import { UserStripeConnectAccount as UserSCA } from "@/models/UserStripeConnectAccount"; +import { UserSubscription, type UserSubscriptionItem } from "@/models/UserSubscription"; +import { WorkOrder, type WorkOrderItem } from "@/models/WorkOrder"; +import { skTypeGuards } from "@/models/_common/skTypeGuards.js"; +import { ddbTable } from "@/models/ddbTable.js"; +import { AuthError, InternalServerError } from "@/utils/httpErrors.js"; +import { logger } from "@/utils/logger.js"; +import type { UserItem } from "@/models/User"; +import type { UserStripeConnectAccountItem } from "@/models/UserStripeConnectAccount"; +import type { PreFetchedUserItems } from "@/types/open-api.js"; + +/** + * ### UserService: queryUserItems + * + * This function queries/fetches/pre-fetches the following types of User items: + * + * - Subscription(s) - used for authentication and authorization + * - StripeConnectAccount - used for authentication and authorization + * - Work Orders - pre-fetched for the user's dashboard + * - Invoices - pre-fetched for the user's dashboard + * - Contacts - pre-fetched for the user's dashboard + * + * ### Items Formatted for the GQL Client Cache + * On the client-side, these pre-fetched items are written into the Apollo client cache + * by a request handler, and are therefore expected to be in the shape specified by the + * GQL schema typedefs. This fn formats the items accordingly. + */ +export const queryUserItems = async ({ + authenticatedUserID, +}: { + authenticatedUserID: UserItem["id"]; +}): Promise<{ + userItems: PreFetchedUserItems; // <-- Returned for delivery to front-end cache + userStripeConnectAccount: UserStripeConnectAccountItem; // <-- Returned for Stripe-API data-refresh fn + userSubscription: UserSubscriptionItem | null; // <-- Returned for Stripe-API data-refresh fn +}> => { + // We want to retrieve items of multiple types, so we don't use a Model-instance here. + const response = await ddbTable.ddbClient.query({ + TableName: ddbTable.tableName, + KeyConditionExpression: `pk = :userID AND sk BETWEEN :skStart AND :skEnd`, + ExpressionAttributeValues: { + ":userID": authenticatedUserID, + ":skStart": `${USER_SK_PREFIX_STR}#${authenticatedUserID}`, + ":skEnd": "~", + // In utf8 byte order, tilde comes after numbers, upper+lowercase letters, #, and $. + }, + Limit: 100, // <-- ensures users with many items don't experience a delayed response + }); + + const items = response.Items; + + // Sanity check: If no items were found, throw AuthError + if (!Array.isArray(items) || items.length === 0) throw new AuthError("User does not exist"); + + // Organize the raw/unaliased items + const { rawSubscription, rawStripeConnectAccount, rawWorkOrders, rawInvoices, rawContacts } = + items.reduce( + ( + accum: { + rawSubscription: UserSubscriptionItem | null; + rawStripeConnectAccount: UserStripeConnectAccountItem | null; + rawWorkOrders: Array; + rawInvoices: Array; + rawContacts: Array; + }, + current + ) => { + // Use type-guards to determine the type of the current item, and add it to the appropriate accum field + if (skTypeGuards.isUser(current)) return accum; + else if (skTypeGuards.isContact(current)) accum.rawContacts.push(current); + else if (skTypeGuards.isInvoice(current)) accum.rawInvoices.push(current); + else if (skTypeGuards.isUserSubscription(current)) accum.rawSubscription = current; + else if (skTypeGuards.isUserSCA(current)) accum.rawStripeConnectAccount = current; + else if (skTypeGuards.isWorkOrder(current)) accum.rawWorkOrders.push(current); + else logger.warn( + `[queryUserItems] The following ITEM was returned by the "queryUserItems" DDB query, but ` + + `no handler has been implemented for items of this type in the item-categorization reducer. + — ITEM: ${safeJsonStringify(current, null, 2)}` + ); // prettier-ignore + + return accum; + }, + { + rawSubscription: null, + rawStripeConnectAccount: null, + rawWorkOrders: [], + rawInvoices: [], + rawContacts: [], + } + ); + + // Ensure the user's SCA was found + if (!rawStripeConnectAccount) + throw new InternalServerError("User's Stripe Connect Account not found"); + + // Format the user's rawStripeConnectAccount object, assign it to returned `userStripeConnectAccount` + const userStripeConnectAccount = + UserSCA.processItemAttributes.fromDB(rawStripeConnectAccount); + + // Format the user's subscription object (this may not exist if the user is not yet subscribed) + const userSubscription = rawSubscription + ? UserSubscription.processItemAttributes.fromDB(rawSubscription) + : null; + + /* Note: workOrders' and invoices' createdByUserID and assignedToUserID fields are converted + into createdBy and assignedTo objects with an "id" field, but no other createdBy/assignedTo + fields can be provided here without fetching additional data on the associated users/contacts + from either the db or usersCache. This function forgoes fetching the data since the client- + side Apollo cache already handles fetching additional data as needed (_if_ it's needed), and + fetching it here can delay auth request response times, especially if the authenticating user + has a large number of workOrders/invoices. */ + + const returnedUserItems: PreFetchedUserItems = { + myWorkOrders: rawWorkOrders.reduce( + (accum: PreFetchedUserItems["myWorkOrders"], rawWorkOrder) => { + // Process workOrder from its raw internal shape: + const { createdByUserID, assignedToUserID, ...workOrderFields } = + WorkOrder.processItemAttributes.fromDB(rawWorkOrder); + + const workOrder = { + // Fields which are nullable/optional in GQL schema must be provided, default to null: + category: null, + checklist: null, + dueDate: null, + entryContact: null, + entryContactPhone: null, + scheduledDateTime: null, + contractorNotes: null, + // workOrder values override above defaults: + ...workOrderFields, + // __typename, createdBy, and assignedTo fields are formatted for the GQL client cache: + __typename: "WorkOrder" as const, + createdBy: { id: createdByUserID }, + assignedTo: assignedToUserID ? { id: assignedToUserID } : null, + }; + + if (createdByUserID === authenticatedUserID) accum.createdByUser.push(workOrder); + else if (assignedToUserID === authenticatedUserID) accum.assignedToUser.push(workOrder); + else logger.warn( + `[queryUserItems] The following WorkOrder was returned by the "queryUserItems" ` + + `DDB query, but it was neither createdBy nor assignedTo the authenticated user. + — AuthenticatedUserID: ${authenticatedUserID} + — WorkOrderItem: ${safeJsonStringify(workOrder, null, 2)}` + ); // prettier-ignore + + return accum; + }, + { createdByUser: [], assignedToUser: [] } + ), + + myInvoices: rawInvoices.reduce( + (accum: PreFetchedUserItems["myInvoices"], rawInvoice) => { + // Process invoice from its raw internal shape: + const { createdByUserID, assignedToUserID, workOrderID, ...invoiceFields } = + Invoice.processItemAttributes.fromDB(rawInvoice); + + const invoice = { + // Fields which are nullable/optional in GQL schema must be provided, default to null: + stripePaymentIntentID: null, + // invoice values override above defaults: + ...invoiceFields, + // __typename, createdBy, and assignedTo fields are formatted for the GQL client cache: + __typename: "Invoice" as const, + createdBy: { id: createdByUserID }, + assignedTo: { id: assignedToUserID }, + workOrder: workOrderID ? { id: workOrderID } : null, + }; + + if (createdByUserID === authenticatedUserID) accum.createdByUser.push(invoice); + else if (assignedToUserID === authenticatedUserID) accum.assignedToUser.push(invoice); + else logger.warn( + `[queryUserItems] The following Invoice was returned by the "queryUserItems" ` + + `DDB query, but it was neither createdBy nor assignedTo the authenticated user. + — AuthenticatedUserID: ${authenticatedUserID} + — WorkOrderItem: ${safeJsonStringify(invoice, null, 2)}` + ); // prettier-ignore + + return accum; + }, + { createdByUser: [], assignedToUser: [] } + ), + myContacts: rawContacts.map((rawContact) => { + // Process contact from its raw internal shape: + const contact = Contact.processItemAttributes.fromDB(rawContact); + + // Fetch some additional data from the usersCache + const { + email = "", // These defaults shouldn't be necessary, but are included for type-safety + phone = "", + profile = { displayName: contact.handle }, // displayName defaults to handle if n/a + } = usersCache.get(contact.handle) ?? {}; + + return { + __typename: "Contact" as const, + id: contact.id, + handle: contact.handle, + email, + phone, + profile, + createdAt: contact.createdAt, + updatedAt: contact.updatedAt, + }; + }), + }; + + return { + userItems: returnedUserItems, + userStripeConnectAccount, + userSubscription, + }; +}; diff --git a/src/services/UserService/registerNewUser.ts b/src/services/UserService/registerNewUser.ts new file mode 100644 index 00000000..2b8c52b3 --- /dev/null +++ b/src/services/UserService/registerNewUser.ts @@ -0,0 +1,138 @@ +import { eventEmitter } from "@/events/eventEmitter.js"; +import { usersCache } from "@/lib/cache/usersCache.js"; +import { stripe } from "@/lib/stripe/stripeClient.js"; +import { Profile, type CreateProfileParams } from "@/models/Profile"; +import { User, type UserItem, type UserCreateItemParams } from "@/models/User"; +import { UserLogin, type LoginParams } from "@/models/UserLogin"; +import { + UserStripeConnectAccount, + type UserStripeConnectAccountItem, +} from "@/models/UserStripeConnectAccount"; +import { UserSCAService } from "@/services/UserSCAService"; +import { UserInputError } from "@/utils/httpErrors.js"; +import { logger } from "@/utils/logger.js"; +import type { UndefinedOnPartialDeep } from "type-fest"; + +export type RegisterNewUserParams = UndefinedOnPartialDeep< + Pick & { + profile: CreateProfileParams | undefined; + } & LoginParams +>; + +/** + * ### UserService: registerNewUser + * + * @returns The newly created User and their Stripe Connect Account. + */ +export const registerNewUser = async ({ + handle, + email, + phone = null, + profile, + password, + googleID, + expoPushToken, +}: RegisterNewUserParams): Promise< + UserItem & { stripeConnectAccount: UserStripeConnectAccountItem } +> => { + // Ensure the user does not already exist + const [user] = await User.query({ where: { email }, limit: 1 }); + + if (user) throw new UserInputError("An account already exists with the provided email address."); + + // Ensure the handle is unique + const userByHandle = usersCache.get(handle); + if (userByHandle) throw new UserInputError("An account already exists with the provided handle."); + + // Create Profile object + const newUserProfile = Profile.fromParams({ ...profile, handle }); + // Create UserLogin object + const newUserLogin = await UserLogin.fromParams({ password, googleID }); + + // Create Stripe Customer via Stripe API + const { id: stripeCustomerID } = await stripe.customers.create({ + email, + ...(phone && { phone }), + ...(!!newUserProfile.displayName && { name: newUserProfile.displayName }), + }); + + // Vars to hold the db items created in the try block: + let newUser: UserItem | undefined; + let newUserStripeConnectAccount: UserStripeConnectAccountItem | undefined; + + try { + // TODO Replace these separate db calls with a single transaction (TransactWriteItems) + + // Create User + newUser = await User.createItem({ + handle, + email, + phone, + ...(expoPushToken && { expoPushToken }), + stripeCustomerID, + profile: { ...newUserProfile }, + login: { ...newUserLogin }, + }); + + // Create newUser's Stripe Connect Account + newUserStripeConnectAccount = await UserSCAService.registerNewUserSCA({ + userID: newUser.id, + email: newUser.email, + phone: newUser.phone, + profile: newUser.profile, + }); + + // Add newUser to usersCache for search-by-handle + usersCache.set(newUser.handle, { + id: newUser.id, + handle: newUser.handle, + email: newUser.email, + phone: newUser.phone ?? null, + profile: newUser.profile, + createdAt: newUser.createdAt, + updatedAt: newUser.updatedAt, + }); + + // Emit the new user event + eventEmitter.emitNewUser(newUser); + // + } catch (error: unknown) { + // Log errors which are not UserInputErrors + if (!(error instanceof UserInputError)) logger.error(error, "UserService.registerNewUser"); + + // This fn returns an error handler for ops that fail in this catch block: + const getCleanupErrHandler = + (descriptionOfItemThatFailedToDelete: string) => (err: unknown) => { + logger.error( + err, + `FAILED TO DELETE ${descriptionOfItemThatFailedToDelete} after failed registration of User with email "${email}".` + ); + }; + + // Delete the Stripe Customer: + await stripe.customers + .del(stripeCustomerID) + .catch(getCleanupErrHandler(`Stripe Customer "${stripeCustomerID}"`)); + + // Delete the User, if it was created: + if (newUser) { + await User.deleteItem({ id: newUser.id }).catch(getCleanupErrHandler(`User "${newUser.id}"`)); + + // Delete the UserStripeConnectAccount, if it was created: + if (newUserStripeConnectAccount) { + await UserStripeConnectAccount.deleteItem({ userID: newUser.id }).catch( + getCleanupErrHandler(`SCA of User "${newUser.id}"`) + ); + } + } + + // Re-throw to allow MW to handle the error sent to the user + throw error; + } + + // If no errors were thrown, return successfully created newUser and SCA + return { + ...newUser, + stripeConnectAccount: newUserStripeConnectAccount, + }; +}; diff --git a/src/services/UserSubscriptionService/UserSubscriptionService.test.ts b/src/services/UserSubscriptionService/UserSubscriptionService.test.ts new file mode 100644 index 00000000..bd0dc42c --- /dev/null +++ b/src/services/UserSubscriptionService/UserSubscriptionService.test.ts @@ -0,0 +1,40 @@ +import { subModelHelpers } from "@/models/UserSubscription/helpers.js"; +import { MOCK_USERS, MOCK_USER_SUBS } from "@/tests/staticMockItems"; +import { UserSubscriptionService } from "./index.js"; + +describe("UserSubscriptionService", () => { + describe("UserSubscriptionService.createSubscription()", () => { + test("returns a valid UserSubscription when called with valid arguments", async () => { + // Arrange mock UserSubscriptions + for (const key in MOCK_USER_SUBS) { + // Get createSubscription inputs from mock UserSub + const mockSub = MOCK_USER_SUBS[key as keyof typeof MOCK_USER_SUBS]; + + // Ascertain the mock User associated with this mock UserSub + const associatedMockUser = + key === "SUB_A" + ? MOCK_USERS.USER_A + : key === "SUB_B" + ? MOCK_USERS.USER_B + : MOCK_USERS.USER_C; + + // Act on the createSubscription method: + const { userSubscription, stripeSubscriptionObject } = + await UserSubscriptionService.createSubscription({ + user: associatedMockUser, + selectedSubscription: mockSub.priceID.split("price_Test")[1] as any, + // all mock sub priceIDs are prefixed with "price_Test" (e.g., "price_TestANNUAL") + }); + + // Assert the results + expect(userSubscription).toStrictEqual({ + ...mockSub, + sk: expect.toSatisfyFn((value: string) => subModelHelpers.sk.isValid(value)), + createdAt: expect.any(Date), + updatedAt: expect.any(Date), + }); + expect(stripeSubscriptionObject).toBeDefined(); + } + }); + }); +}); diff --git a/src/services/UserSubscriptionService/createSubscription.ts b/src/services/UserSubscriptionService/createSubscription.ts new file mode 100644 index 00000000..77dea893 --- /dev/null +++ b/src/services/UserSubscriptionService/createSubscription.ts @@ -0,0 +1,71 @@ +import { promoCodesCache } from "@/lib/cache/promoCodesCache.js"; +import { stripe } from "@/lib/stripe/stripeClient.js"; +import { UserSubscription, type UserSubscriptionItem } from "@/models/UserSubscription"; +import { SUBSCRIPTION_PRICE_NAMES as SUB_PRICE_NAMES } from "@/models/UserSubscription/enumConstants.js"; +import { UserInputError } from "@/utils/httpErrors.js"; +import { normalizeStripeFields } from "./normalizeStripeFields.js"; +import type { StripeSubscriptionWithClientSecret } from "@/lib/stripe/types.js"; +import type { UserItem } from "@/models/User"; +import type { SubscriptionPriceName } from "@/types/graphql.js"; +import type Stripe from "stripe"; + +/** + * `UserSubscriptionService`.{@link createSubscription} params + */ +export type CreateSubscriptionParams = { + user: Pick; + selectedSubscription: SubscriptionPriceName; + promoCode?: string | undefined; +}; + +/** + * Creates a new {@link Stripe.Subscription|Subscription} via Stripe for the + * provided user, and upserts the subscription info to the database. + */ +export const createSubscription = async ({ + user: { id: userID, stripeCustomerID }, + selectedSubscription, + promoCode, +}: CreateSubscriptionParams): Promise<{ + userSubscription: UserSubscriptionItem; + stripeSubscriptionObject: Stripe.Response; +}> => { + // Ascertain the subscription's Stripe price ID + const priceID = UserSubscription.PRICE_IDS[selectedSubscription]; + // Sanity check - ensure a priceID exists for the selected subscription + if (!priceID) throw new UserInputError("Invalid subscription"); + + // Ascertain the subscription's Stripe promoCode ID if applicable + const promoCodeID = promoCodesCache.get(promoCode ?? "")?.id; + + // Submit info to Stripe API for new subscription + const stripeSubObject = (await stripe.subscriptions.create({ + customer: stripeCustomerID, + items: [{ price: priceID }], + ...(promoCodeID && { promotion_code: promoCodeID }), + ...(selectedSubscription === SUB_PRICE_NAMES.TRIAL && { trial_period_days: 14 }), + payment_behavior: "default_incomplete", + payment_settings: { save_default_payment_method: "on_subscription" }, + expand: ["latest_invoice.payment_intent", "customer"], + })) as Stripe.Response; + + // Get the fields needed from the returned object + const { createdAt, currentPeriodEnd, productID } = normalizeStripeFields(stripeSubObject); + + // Upsert the sub info to ensure db is up to date and prevent duplicate user subs. + const userSubscription = await UserSubscription.upsertItem({ + userID, + id: stripeSubObject.id, + currentPeriodEnd, + productID, + priceID, + status: stripeSubObject.status, + createdAt, + }); + + // Return both the API UserSub object and the Stripe response object + return { + userSubscription, + stripeSubscriptionObject: stripeSubObject, + }; +}; diff --git a/src/services/UserSubscriptionService/findUsersSubscription.ts b/src/services/UserSubscriptionService/findUsersSubscription.ts new file mode 100644 index 00000000..9b43609d --- /dev/null +++ b/src/services/UserSubscriptionService/findUsersSubscription.ts @@ -0,0 +1,65 @@ +import { UserSubscription, type UserSubscriptionItem } from "@/models/UserSubscription"; + +/** + * Although unlikely, it is possible for users to have multiple subs. + * To guard against these edge cases, this query returns the most recently + * created subscription with an "active" status. See below comment. + * + * currentSub win conditions in order of precedence: + * - currentSub is 1st in subs array + * - currentSub.status is "active" and subToReturn is NOT "active" + * - currentSub.status is "active" and created more recently than subToReturn + * - neither are "active", and currentSub was updated more recently than subToReturn + * + */ +export const findUsersSubscription = async ({ + authenticatedUserID, +}: { + authenticatedUserID: string; +}) => { + /* + Although unlikely, it is possible for users to have multiple subs. + To guard against these edge cases, this query returns the most recently + created subscription with an "active" status. See below comment. + + currentSub win conditions in order of precedence: + - currentSub is 1st in subs array + - currentSub.status is "active" and subToReturn is NOT "active" + - currentSub.status is "active" and created more recently than subToReturn + - neither are "active", and currentSub was updated more recently than subToReturn + */ + const subs = await UserSubscription.query({ + where: { + userID: authenticatedUserID, + sk: { beginsWith: UserSubscription.SK_PREFIX }, + }, + }); + + return subs.reduce((subToReturn, currentSub) => { + if ( + currentSub.status === "active" && + (subToReturn.status !== "active" || wasCreatedEarlier(currentSub, subToReturn)) + ) { + subToReturn = currentSub; + } else if (subToReturn.status !== "active" && wasUpdatedLater(currentSub, subToReturn)) { + subToReturn = currentSub; + } + return subToReturn; + }); +}; + +// Below: quick utils for making above boolean expressions a little easier to read + +const wasCreatedEarlier = ( + { createdAt: createdAt_1 }: UserSubscriptionItem, + { createdAt: createdAt_2 }: UserSubscriptionItem +) => { + return createdAt_1 < createdAt_2; +}; + +const wasUpdatedLater = ( + { updatedAt: updatedAt_1 }: UserSubscriptionItem, + { updatedAt: updatedAt_2 }: UserSubscriptionItem +) => { + return updatedAt_1 > updatedAt_2; +}; diff --git a/src/services/UserSubscriptionService/index.ts b/src/services/UserSubscriptionService/index.ts new file mode 100644 index 00000000..aa171e4a --- /dev/null +++ b/src/services/UserSubscriptionService/index.ts @@ -0,0 +1,16 @@ +import { createSubscription } from "./createSubscription.js"; +import { findUsersSubscription } from "./findUsersSubscription.js"; +import { normalizeStripeFields } from "./normalizeStripeFields.js"; +import { refreshDataFromStripe } from "./refreshDataFromStripe.js"; + +/** + * #### UserSubscriptionService + * + * This object contains methods which implement business logic for User Subscription operations. + */ +export const UserSubscriptionService = { + createSubscription, + findUsersSubscription, + normalizeStripeFields, + refreshDataFromStripe, +}; diff --git a/src/models/UserSubscription/normalizeStripeFields.ts b/src/services/UserSubscriptionService/normalizeStripeFields.ts similarity index 80% rename from src/models/UserSubscription/normalizeStripeFields.ts rename to src/services/UserSubscriptionService/normalizeStripeFields.ts index 3d165d1e..e3081ab8 100644 --- a/src/models/UserSubscription/normalizeStripeFields.ts +++ b/src/services/UserSubscriptionService/normalizeStripeFields.ts @@ -1,6 +1,5 @@ import { isString } from "@nerdware/ts-type-safety-utils"; -import { unixToDate } from "@/utils/formatters/dateTime.js"; -import type { StripeSubscriptionWithClientSecret } from "@/lib/stripe/types.js"; +import { unixTimestampToDate } from "@/utils/formatters/dateTime.js"; import type Stripe from "stripe"; /** @@ -17,18 +16,14 @@ import type Stripe from "stripe"; * | `productID` | `string` | `items.data[0].price.product` | `string` | * | `priceID` | `string` | `items.data[0].price.id` | `string` | */ -export const normalizeStripeFields = < - StripeSub extends Stripe.Subscription | StripeSubscriptionWithClientSecret = Stripe.Subscription, ->( +export const normalizeStripeFields = ( stripeSubObject: StripeSub ) => { // Destructure fields which are relevant to the app: const { customer, created, current_period_end, items } = stripeSubObject; // Ensure there's at least 1 subscription-item in the `items` array: - if (!items.data[0]) { - throw new Error("Stripe Subscription object has no items."); - } + if (!items.data[0]) throw new Error("Stripe Subscription object has no items."); // Get the `id` and related `product` of the `price` of the first sub-item: const { id: priceID, product } = items.data[0].price; @@ -36,8 +31,8 @@ export const normalizeStripeFields = < // Obtain+normalize fields used in the app: const appFields = { stripeCustomerID: isString(customer) ? customer : customer.id, - createdAt: unixToDate(created), - currentPeriodEnd: unixToDate(current_period_end), + createdAt: unixTimestampToDate(created), + currentPeriodEnd: unixTimestampToDate(current_period_end), productID: isString(product) ? product : product.id, priceID, }; diff --git a/src/services/UserSubscriptionService/refreshDataFromStripe.ts b/src/services/UserSubscriptionService/refreshDataFromStripe.ts new file mode 100644 index 00000000..cb737bc9 --- /dev/null +++ b/src/services/UserSubscriptionService/refreshDataFromStripe.ts @@ -0,0 +1,49 @@ +import { stripe } from "@/lib/stripe/stripeClient.js"; +import { UserSubscription, type UserSubscriptionItem } from "@/models/UserSubscription"; +import { normalizeStripeFields } from "./normalizeStripeFields.js"; + +/** + * Fetches up-to-date Subscription data from Stripe and updates the DB if necessary. + * + * - `UserSubscriptionItem` fields kept in sync with Stripe: + * - `status` + * - `currentPeriodEnd` + * + * - `UserSubscriptionItem` fields needed to update the db, if necessary: + * - `userID` + * - `sk` (the UserSubscriptionItem SK) + * + * @returns Up-to-date UserSubscription fields. + */ +export const refreshDataFromStripe = async ( + existingSub: UserSubscriptionItem +): Promise => { + // Fetch fresh data from Stripe + const stripeRetrieveSubResult = await stripe.subscriptions.retrieve(existingSub.id); + + // Normalize the object returned from Stripe + const stripeSub = normalizeStripeFields(stripeRetrieveSubResult); + + // If DB values are stale, update the user's Sub in the db + if ( + stripeSub.status !== existingSub.status || + stripeSub.currentPeriodEnd.getTime() !== existingSub.currentPeriodEnd.getTime() + ) { + // Return the updated Sub + return await UserSubscription.updateItem( + { + userID: existingSub.userID, + sk: existingSub.sk, + }, + { + update: { + status: stripeSub.status, + currentPeriodEnd: stripeSub.currentPeriodEnd, + }, + } + ); + } + + // If no update is required, return the existing Sub fields + return existingSub; +}; diff --git a/src/services/WorkOrderService/cancelWorkOrder.ts b/src/services/WorkOrderService/cancelWorkOrder.ts new file mode 100644 index 00000000..7482542b --- /dev/null +++ b/src/services/WorkOrderService/cancelWorkOrder.ts @@ -0,0 +1,58 @@ +import { eventEmitter } from "@/events/eventEmitter.js"; +import { WorkOrder, type WorkOrderItem } from "@/models/WorkOrder"; +import { AuthService } from "@/services/AuthService"; +import { UserInputError } from "@/utils/httpErrors.js"; + +export const cancelWorkOrder = async ({ + workOrderID, + authenticatedUserID, +}: { + workOrderID: string; + authenticatedUserID: string; +}): Promise<{ deleted: boolean; workOrder: WorkOrderItem }> => { + const [existingWO] = await WorkOrder.query({ + where: { id: workOrderID }, + limit: 1, + }); + + if (!existingWO) + throw new UserInputError("A work order with the provided ID could not be found."); + + AuthService.verifyUserIsAuthorized.toPerformThisUpdate(existingWO, { + idOfUserWhoCanPerformThisUpdate: existingWO.createdByUserID, + authenticatedUserID, + forbiddenStatuses: { + CANCELLED: "The requested work order has already been cancelled.", + COMPLETE: "The requested work order has already been completed and cannot be cancelled.", + }, + }); + + let deleted = false; + let workOrder = existingWO; + + /* If status is UNASSIGNED, delete it and return a DeleteMutationResponse. + Otherwise, update WO status to CANCELLED and return the updated WorkOrder.*/ + + if (existingWO.status === "UNASSIGNED") { + await WorkOrder.deleteItem({ createdByUserID: authenticatedUserID, id: workOrderID }); + deleted = true; + } else { + const canceledWO = await WorkOrder.updateItem( + { id: existingWO.id, createdByUserID: existingWO.createdByUserID }, + { + update: { + status: "CANCELLED", + }, + } + ); + + eventEmitter.emitWorkOrderCancelled(canceledWO); + + workOrder = canceledWO; + } + + return { + deleted, + workOrder, + }; +}; diff --git a/src/services/WorkOrderService/createWorkOrder.ts b/src/services/WorkOrderService/createWorkOrder.ts new file mode 100644 index 00000000..314848f6 --- /dev/null +++ b/src/services/WorkOrderService/createWorkOrder.ts @@ -0,0 +1,25 @@ +import { eventEmitter } from "@/events/eventEmitter.js"; +import { Location } from "@/models/Location"; +import { WorkOrder } from "@/models/WorkOrder"; +import type { CreateWorkOrderInput } from "@/types/graphql.js"; + +/** + * ### WorkOrderService: createWorkOrder + */ +export const createWorkOrder = async ( + woInput: { createdByUserID: string } & CreateWorkOrderInput +) => { + const { createdByUserID, assignedTo = "UNASSIGNED", location, ...createWorkOrderInput } = woInput; + + const newWO = await WorkOrder.createItem({ + createdByUserID, + assignedToUserID: assignedTo, + status: assignedTo === "UNASSIGNED" ? "UNASSIGNED" : "ASSIGNED", + location: Location.fromParams(location), + ...createWorkOrderInput, + }); + + eventEmitter.emitWorkOrderCreated(newWO); + + return newWO; +}; diff --git a/src/services/WorkOrderService/findWorkOrderByID.ts b/src/services/WorkOrderService/findWorkOrderByID.ts new file mode 100644 index 00000000..a25ff8a7 --- /dev/null +++ b/src/services/WorkOrderService/findWorkOrderByID.ts @@ -0,0 +1,17 @@ +import { WorkOrder } from "@/models/WorkOrder"; +import { UserInputError } from "@/utils/httpErrors.js"; + +/** + * ### WorkOrderService: findWorkOrderByID + */ +export const findWorkOrderByID = async ({ workOrderID }: { workOrderID: string }) => { + const [workOrder] = await WorkOrder.query({ + where: { id: workOrderID }, + limit: 1, + }); + + if (!workOrder) + throw new UserInputError("A wwork order with the provided ID could not be found."); + + return workOrder; +}; diff --git a/src/services/WorkOrderService/index.ts b/src/services/WorkOrderService/index.ts new file mode 100644 index 00000000..b26d0e10 --- /dev/null +++ b/src/services/WorkOrderService/index.ts @@ -0,0 +1,20 @@ +import { cancelWorkOrder } from "./cancelWorkOrder.js"; +import { createWorkOrder } from "./createWorkOrder.js"; +import { findWorkOrderByID } from "./findWorkOrderByID.js"; +import { queryUsersWorkOrders } from "./queryUsersWorkOrders.js"; +import { setWorkOrderStatusComplete } from "./setWorkOrderStatusComplete.js"; +import { updateWorkOrder } from "./updateWorkOrder.js"; + +/** + * #### WorkOrderService + * + * This object contains methods which implement business logic for WorkOrder operations. + */ +export const WorkOrderService = { + cancelWorkOrder, + createWorkOrder, + findWorkOrderByID, + queryUsersWorkOrders, + setWorkOrderStatusComplete, + updateWorkOrder, +} as const; diff --git a/src/services/WorkOrderService/queryUsersWorkOrders.ts b/src/services/WorkOrderService/queryUsersWorkOrders.ts new file mode 100644 index 00000000..dabf5915 --- /dev/null +++ b/src/services/WorkOrderService/queryUsersWorkOrders.ts @@ -0,0 +1,28 @@ +import { WorkOrder } from "@/models/WorkOrder"; +import type { AuthTokenPayload } from "@/types/open-api.js"; + +/** + * ### WorkOrderService: queryUsersWorkOrders + */ +export const queryUsersWorkOrders = async ({ + authenticatedUser, +}: { + authenticatedUser: AuthTokenPayload; +}) => { + return { + // Query for all WorkOrders created by the authenticated User + createdByUser: await WorkOrder.query({ + where: { + createdByUserID: authenticatedUser.id, + id: { beginsWith: WorkOrder.SK_PREFIX }, + }, + }), + // Query for all WorkOrders assigned to the authenticated User + assignedToUser: await WorkOrder.query({ + where: { + assignedToUserID: authenticatedUser.id, + id: { beginsWith: WorkOrder.SK_PREFIX }, + }, + }), + }; +}; diff --git a/src/services/WorkOrderService/setWorkOrderStatusComplete.ts b/src/services/WorkOrderService/setWorkOrderStatusComplete.ts new file mode 100644 index 00000000..6d7fffb1 --- /dev/null +++ b/src/services/WorkOrderService/setWorkOrderStatusComplete.ts @@ -0,0 +1,43 @@ +import { eventEmitter } from "@/events/eventEmitter.js"; +import { WorkOrder } from "@/models/WorkOrder"; +import { AuthService } from "@/services/AuthService"; +import { UserInputError } from "@/utils/httpErrors.js"; + +export const setWorkOrderStatusComplete = async ({ + workOrderID, + authenticatedUserID, +}: { + workOrderID: string; + authenticatedUserID: string; +}) => { + const [existingWO] = await WorkOrder.query({ + where: { id: workOrderID }, + limit: 1, + }); + + if (!existingWO) + throw new UserInputError("A work order with the provided ID could not be found."); + + AuthService.verifyUserIsAuthorized.toPerformThisUpdate(existingWO, { + idOfUserWhoCanPerformThisUpdate: existingWO.assignedToUserID, + authenticatedUserID, + forbiddenStatuses: { + UNASSIGNED: "Only the work order's assignee may perform this update.", + CANCELLED: "The requested work order has been cancelled and cannot be marked complete.", + COMPLETE: "The requested work order has already been marked complete.", + }, + }); + + const updatedWO = await WorkOrder.updateItem( + { id: existingWO.id, createdByUserID: existingWO.createdByUserID }, + { + update: { + status: "COMPLETE", + }, + } + ); + + eventEmitter.emitWorkOrderCompleted(updatedWO); + + return updatedWO; +}; diff --git a/src/services/WorkOrderService/updateWorkOrder.ts b/src/services/WorkOrderService/updateWorkOrder.ts new file mode 100644 index 00000000..a7baa3ae --- /dev/null +++ b/src/services/WorkOrderService/updateWorkOrder.ts @@ -0,0 +1,69 @@ +import { eventEmitter } from "@/events/eventEmitter.js"; +import { Location } from "@/models/Location"; +import { userModelHelpers } from "@/models/User/helpers.js"; +import { WorkOrder } from "@/models/WorkOrder"; +import { AuthService } from "@/services/AuthService"; +import { UserInputError } from "@/utils/httpErrors.js"; +import type { UpdateWorkOrderInput } from "@/types/graphql.js"; + +export const updateWorkOrder = async ({ + workOrderID, + update, + authenticatedUserID, +}: { + workOrderID: string; + update: UpdateWorkOrderInput; + authenticatedUserID: string; +}) => { + const [existingWO] = await WorkOrder.query({ + where: { id: workOrderID }, + limit: 1, + }); + + if (!existingWO) + throw new UserInputError("A work order with the provided ID could not be found."); + + AuthService.verifyUserIsAuthorized.toPerformThisUpdate(existingWO, { + idOfUserWhoCanPerformThisUpdate: existingWO.createdByUserID, + authenticatedUserID, + forbiddenStatuses: { + CANCELLED: "The requested work order has been cancelled and cannot be updated.", + }, + }); + + /* Check if woInput args necessitate updating the STATUS: + - If the existingWO.status is ASSIGNED and woInput.assignedToUserID is UNASSIGNED, + then the status should be updated to UNASSIGNED. + - If the existingWO.status is UNASSIGNED and woInput.assignedToUserID is a valid + User/Contact ID, then the status should be updated to ASSIGNED. + - Otherwise, the status should remain unchanged. */ + const upToDateStatus = + existingWO.status === "ASSIGNED" && update.assignedToUserID === "UNASSIGNED" + ? "UNASSIGNED" + : existingWO.status === "UNASSIGNED" && + userModelHelpers.id.isValid(update.assignedToUserID?.replace(/CONTACT#/, "")) + ? "ASSIGNED" + : existingWO.status; + + /* Extract `location`, and if provided, provide it to Location.fromParams. + Note that `fromParams` will throw if required fields are not present. Since + Location's are stored as compound-string attributes, they can not be partially + updated, i.e., if it's desirable to only change `streetLine1`, it can not be + updated without all the other Location fields being provided as well. */ + const { location, ...woFieldsToUpdate } = update; + + const updatedWO = await WorkOrder.updateItem( + { id: existingWO.id, createdByUserID: existingWO.createdByUserID }, + { + update: { + ...woFieldsToUpdate, + ...(!!location && { location: Location.fromParams(location) }), + status: upToDateStatus, + }, + } + ); + + eventEmitter.emitWorkOrderUpdated(updatedWO, existingWO); + + return updatedWO; +}; diff --git a/src/tests/e2e.adminRoutes.test.ts b/src/tests/e2e.adminRoutes.test.ts index 843493ee..1fdc029e 100644 --- a/src/tests/e2e.adminRoutes.test.ts +++ b/src/tests/e2e.adminRoutes.test.ts @@ -1,15 +1,13 @@ import request from "supertest"; -import { expressApp } from "@/expressApp.js"; -import { ENV } from "@/server/env"; -import type { Server } from "http"; +import { httpServer, type HttpServerWithCustomStart } from "@/httpServer.js"; vi.mock("@/apolloServer.js"); describe("[e2e][Server Requests] Routes /api/admin/*", () => { - let server: Server; + let server: HttpServerWithCustomStart; - beforeAll(() => { - server = expressApp.listen(ENV.CONFIG.PORT); + beforeAll(async () => { + server = await httpServer.start(0); }); afterAll(() => { @@ -18,20 +16,21 @@ describe("[e2e][Server Requests] Routes /api/admin/*", () => { describe("GET /api/admin/healthcheck", () => { test(`returns "SUCCESS" message in request body`, async () => { - const response = await request(expressApp).get("/api/admin/healthcheck"); + const response = await request(httpServer).get("/api/admin/healthcheck"); + expect(response.statusCode).toBe(200); - expect(response.body?.message).toBe("SUCESS"); + expect(response.body?.message).toBe("SUCCESS"); }); }); describe("POST /api/admin/csp-violation", () => { - test("returns response status code 200", async () => { - const response = await request(expressApp) + test("returns response status code 204", async () => { + const response = await request(httpServer) .post("/api/admin/csp-violation") .send({ "csp-report": JSON.stringify({ "csp-report": "MOCK_CSP_VIOLATION_REPORT" }), }); - expect(response.statusCode).toBe(200); + expect(response.statusCode).toBe(204); }); }); }); diff --git a/src/tests/e2e.authRoutes.test.ts b/src/tests/e2e.authRoutes.test.ts index e8adb739..6d239642 100644 --- a/src/tests/e2e.authRoutes.test.ts +++ b/src/tests/e2e.authRoutes.test.ts @@ -1,12 +1,12 @@ import { isString, isPlainObject } from "@nerdware/ts-type-safety-utils"; import request from "supertest"; -import { expressApp } from "@/expressApp.js"; +import { httpServer, type HttpServerWithCustomStart } from "@/httpServer.js"; import { usersCache } from "@/lib/cache/usersCache.js"; import { stripe } from "@/lib/stripe/stripeClient.js"; -import { User } from "@/models/User/User.js"; -import { USER_ID_REGEX } from "@/models/User/regex.js"; +import { User, userModelHelpers } from "@/models/User"; import { ddbTable } from "@/models/ddbTable.js"; -import { ENV } from "@/server/env"; +import { AuthService } from "@/services/AuthService"; +import { AuthToken } from "@/services/AuthService/AuthToken.js"; import { MOCK_USERS, MOCK_CONTACTS, @@ -21,17 +21,16 @@ import { UNALIASED_MOCK_INVOICES, MOCK_USER_SCAs, } from "@/tests/staticMockItems"; -import { AuthToken } from "@/utils/AuthToken.js"; +import { JWT } from "@/utils/jwt.js"; import { passwordHasher } from "@/utils/passwordHasher.js"; -import type { Server } from "http"; vi.mock("@/apolloServer.js"); describe("[e2e] Server Requests /api/auth/*", () => { - let server: Server; + let server: HttpServerWithCustomStart; - beforeAll(() => { - server = expressApp.listen(ENV.CONFIG.PORT); + beforeAll(async () => { + server = await httpServer.start(0); }); afterAll(() => { @@ -45,22 +44,21 @@ describe("[e2e] Server Requests /api/auth/*", () => { handle: "@mock_user", email: "mock_user@gmail.com", phone: "(123) 456-7890", - type: "LOCAL", password: "MockPassword@1", }; // Mock StripeAPI values returned by below stubs: const MOCK_STRIPE_CUSTOMER_ID = "cus_TestTestTest"; const MOCK_STRIPE_CONNECT_ACCOUNT_ID = "acct_TestTestTest"; - // Stub empty response from User.query in findUserByEmail middleware + // Stub empty response from User.query in UserService.registerNewUser method vi.spyOn(User, "query").mockResolvedValueOnce([]); - // Stub stripe.customers.create for invocation in User.createOne method + // Stub stripe.customers.create for invocation in UserService.registerNewUser method vi.spyOn(stripe.customers, "create").mockResolvedValueOnce({ id: MOCK_STRIPE_CUSTOMER_ID, } as any); - // Stub stripe.accounts.create for invocation in UserStripeConnectAccount.createOne method + // Stub stripe.accounts.create for invocation in UserSCAService.registerNewUserSCA method vi.spyOn(stripe.accounts, "create").mockResolvedValueOnce({ id: MOCK_STRIPE_CONNECT_ACCOUNT_ID, details_submitted: false, @@ -69,19 +67,19 @@ describe("[e2e] Server Requests /api/auth/*", () => { } as any); // Send the request - const response = await request(expressApp) + const response = await request(httpServer) .post("/api/auth/register") .send(MOCK_REGISTER_REQUEST_ARGS); // Assert the response - expect(response.statusCode).toBe(200); + expect(response.statusCode).toBe(201); assert(isString(response.body?.token), "response.body.token is not a string"); // Assert the token payload - const tokenPayload = await AuthToken.validateAndDecodeAuthToken(response.body.token); + const tokenPayload = await AuthToken.validateAndDecode(response.body.token); expect(tokenPayload).toStrictEqual({ - id: expect.stringMatching(USER_ID_REGEX), + id: expect.stringMatching(userModelHelpers.id.regex), handle: MOCK_REGISTER_REQUEST_ARGS.handle, email: MOCK_REGISTER_REQUEST_ARGS.email, phone: MOCK_REGISTER_REQUEST_ARGS.phone, @@ -93,6 +91,7 @@ describe("[e2e] Server Requests /api/auth/*", () => { chargesEnabled: false, payoutsEnabled: false, }, + subscription: null, createdAt: expect.toBeValidDate(), updatedAt: expect.toBeValidDate(), }); @@ -107,11 +106,11 @@ describe("[e2e] Server Requests /api/auth/*", () => { password: "MockPassword@1", }; - // Stub the User.query in findUserByEmail middleware + // Stub the User.query in the find-User-by-email logic vi.spyOn(User, "query").mockResolvedValueOnce([MOCK_USERS.USER_A]); - // Stub the passwordHasher.validate response in validatePassword middleware + // Stub the passwordHasher.validate response in validate-password logic vi.spyOn(passwordHasher, "validate").mockResolvedValueOnce(true); - // Stub ddbTable.ddbClient.query response in queryUserItems middleware + // Stub ddbTable.ddbClient.query response in query-UserItems logic vi.spyOn(ddbTable.ddbClient, "query").mockResolvedValueOnce({ $metadata: {}, Items: [ @@ -120,16 +119,18 @@ describe("[e2e] Server Requests /api/auth/*", () => { UNALIASED_MOCK_USER_SUBS.SUB_A, UNALIASED_MOCK_CONTACTS.CONTACT_A, UNALIASED_MOCK_WORK_ORDERS.WO_A, + UNALIASED_MOCK_WORK_ORDERS.WO_B, UNALIASED_MOCK_INVOICES.INV_A, + UNALIASED_MOCK_INVOICES.INV_B, ], }); - // Stub usersCache.get response in queryUserItems middleware + // Stub usersCache.get response in queryUserItems fn vi.spyOn(usersCache, "get").mockReturnValueOnce(MOCK_USERS.USER_B); - // Stub User.updateItem into a no-op in updateExpoPushToken middleware + // Stub User.updateItem into a no-op in update-ExpoPushToken logic vi.spyOn(User, "updateItem").mockResolvedValueOnce({} as any); // Send the request - const { status, body: responseBody } = await request(expressApp) + const { status, body: responseBody } = await request(httpServer) .post("/api/auth/login") .send(MOCK_LOGIN_REQUEST_ARGS); @@ -139,10 +140,10 @@ describe("[e2e] Server Requests /api/auth/*", () => { assert(isPlainObject(responseBody?.userItems), "response.body.userItems is not present"); // Assert the token payload - const tokenPayload = await AuthToken.validateAndDecodeAuthToken(responseBody.token); + const tokenPayload = await AuthToken.validateAndDecode(responseBody.token); expect(tokenPayload).toStrictEqual({ - id: expect.stringMatching(USER_ID_REGEX), + id: expect.stringMatching(userModelHelpers.id.regex), handle: MOCK_USERS.USER_A.handle, email: MOCK_LOGIN_REQUEST_ARGS.email, phone: MOCK_USERS.USER_A.phone, @@ -163,50 +164,263 @@ describe("[e2e] Server Requests /api/auth/*", () => { updatedAt: expect.toBeValidDate(), }); - // Expected userItems: + // Expected WO+INV userItems: const { createdByUserID: WO_A_createdByUserID, - assignedToUserID: WO_A_assignedToUserID, // null + assignedToUserID: _WO_A_assignedToUserID, // null ...WO_A_fields } = MOCK_WORK_ORDERS.WO_A; + const { + createdByUserID: WO_B_createdByUserID, + assignedToUserID: WO_B_assignedToUserID, + ...WO_B_fields + } = MOCK_WORK_ORDERS.WO_B; const { createdByUserID: INV_A_createdByUserID, assignedToUserID: INV_A_assignedToUserID, - workOrderID: INV_A_workOrderID, // null + workOrderID: _INV_A_workOrderID, // null ...INV_A_fields } = MOCK_INVOICES.INV_A; + const { + createdByUserID: INV_B_createdByUserID, + assignedToUserID: INV_B_assignedToUserID, + workOrderID: _INV_B_workOrderID, // null + ...INV_B_fields + } = MOCK_INVOICES.INV_B; // Assert the pre-fetched userItems expect(responseBody.userItems).toStrictEqual({ - workOrders: [ - expect.objectContaining({ - ...WO_A_fields, - createdBy: { id: WO_A_createdByUserID }, - assignedTo: null, - createdAt: expect.toBeValidDate(), - updatedAt: expect.toBeValidDate(), - }), - ], - invoices: [ - expect.objectContaining({ - ...INV_A_fields, - createdBy: { id: INV_A_createdByUserID }, - assignedTo: { id: INV_A_assignedToUserID }, - workOrder: INV_A_workOrderID, // null - createdAt: expect.toBeValidDate(), - updatedAt: expect.toBeValidDate(), - }), - ], - contacts: [ - expect.objectContaining({ + myWorkOrders: { + createdByUser: [ + { + ...WO_A_fields, + __typename: "WorkOrder", + createdBy: { id: WO_A_createdByUserID }, + assignedTo: null, + location: { ...WO_A_fields.location }, // rm Location class prototype + createdAt: WO_A_fields.createdAt.toISOString(), + updatedAt: WO_A_fields.updatedAt.toISOString(), + }, + ], + assignedToUser: [ + { + ...WO_B_fields, + __typename: "WorkOrder", + createdBy: { id: WO_B_createdByUserID }, + assignedTo: { id: WO_B_assignedToUserID }, + location: { ...WO_B_fields.location }, // rm Location class prototype + dueDate: WO_B_fields.dueDate.toISOString(), + scheduledDateTime: WO_B_fields.scheduledDateTime.toISOString(), + createdAt: WO_B_fields.createdAt.toISOString(), + updatedAt: WO_B_fields.updatedAt.toISOString(), + }, + ], + }, + myInvoices: { + createdByUser: [ + { + ...INV_A_fields, + __typename: "Invoice", + createdBy: { id: INV_A_createdByUserID }, + assignedTo: { id: INV_A_assignedToUserID }, + workOrder: null, + createdAt: INV_A_fields.createdAt.toISOString(), + updatedAt: INV_A_fields.updatedAt.toISOString(), + }, + ], + assignedToUser: [ + { + ...INV_B_fields, + __typename: "Invoice", + createdBy: { id: INV_B_createdByUserID }, + assignedTo: { id: INV_B_assignedToUserID }, + workOrder: null, + createdAt: INV_B_fields.createdAt.toISOString(), + updatedAt: INV_B_fields.updatedAt.toISOString(), + }, + ], + }, + myContacts: [ + { + __typename: "Contact", id: MOCK_CONTACTS.CONTACT_A.id, handle: MOCK_CONTACTS.CONTACT_A.handle, email: MOCK_USERS.USER_B.email, phone: MOCK_USERS.USER_B.phone, profile: MOCK_USERS.USER_B.profile, - createdAt: expect.toBeValidDate(), - updatedAt: expect.toBeValidDate(), - }), + createdAt: MOCK_CONTACTS.CONTACT_A.createdAt.toISOString(), + updatedAt: MOCK_CONTACTS.CONTACT_A.updatedAt.toISOString(), + }, + ], + }); + }); + }); + + describe("POST /api/auth/google-token", () => { + test("returns a valid Fixit AuthToken and pre-fetched user-items in response body", async () => { + // Arrange mock client request args to provide in req.body: + const MOCK_GOOGLE_TOKEN_REQUEST_ARGS = { + googleIDToken: JWT.signAndEncode({ + email: MOCK_USERS.USER_B.email, + googleID: MOCK_USERS.USER_B.login.googleID, + id: MOCK_USERS.USER_B.login.googleID, // for jwt `subject` field + }), + }; + + // Stub AuthService.parseGoogleOAuth2IDToken + vi.spyOn(AuthService, "parseGoogleOAuth2IDToken").mockResolvedValueOnce({ + email: MOCK_USERS.USER_B.email, + googleID: MOCK_USERS.USER_B.login.googleID, + profile: { + givenName: MOCK_USERS.USER_B.profile.givenName, + familyName: MOCK_USERS.USER_B.profile.familyName, + photoUrl: MOCK_USERS.USER_B.profile.photoUrl, + }, + }); + + // Stub the User.query in the find-User-by-email logic + vi.spyOn(User, "query").mockResolvedValueOnce([MOCK_USERS.USER_B]); + // Stub ddbTable.ddbClient.query response in query-UserItems logic + vi.spyOn(ddbTable.ddbClient, "query").mockResolvedValueOnce({ + $metadata: {}, + Items: [ + UNALIASED_MOCK_USERS.USER_B, + UNALIASED_MOCK_USER_SCAs.SCA_B, + UNALIASED_MOCK_USER_SUBS.SUB_B, + UNALIASED_MOCK_CONTACTS.CONTACT_C, + UNALIASED_MOCK_WORK_ORDERS.WO_B, + UNALIASED_MOCK_INVOICES.INV_A, + UNALIASED_MOCK_INVOICES.INV_B, + UNALIASED_MOCK_INVOICES.INV_C, + ], + }); + // Stub usersCache.get response in queryUserItems fn + vi.spyOn(usersCache, "get").mockReturnValueOnce(MOCK_USERS.USER_A); + // Stub User.updateItem into a no-op in update-ExpoPushToken logic + vi.spyOn(User, "updateItem").mockResolvedValueOnce({} as any); + + // Send the request + const { status, body: responseBody } = await request(httpServer) + .post("/api/auth/google-token") + .send(MOCK_GOOGLE_TOKEN_REQUEST_ARGS); + + // Assert the response + expect(status).toBe(200); + assert(isString(responseBody?.token), "response.body.token is not present"); + assert(isPlainObject(responseBody?.userItems), "response.body.userItems is not present"); + + // Assert the token payload + const tokenPayload = await AuthToken.validateAndDecode(responseBody.token); + + expect(tokenPayload).toStrictEqual({ + id: expect.stringMatching(userModelHelpers.id.regex), + handle: MOCK_USERS.USER_B.handle, + email: MOCK_USERS.USER_B.email, + phone: MOCK_USERS.USER_B.phone, + profile: MOCK_USERS.USER_B.profile, + stripeCustomerID: MOCK_USERS.USER_B.stripeCustomerID, + stripeConnectAccount: { + id: MOCK_USER_SCAs.SCA_B.id, + detailsSubmitted: true, + chargesEnabled: true, + payoutsEnabled: true, + }, + subscription: { + id: MOCK_USER_SUBS.SUB_B.id, + status: "active", + currentPeriodEnd: expect.toBeValidDate(), + }, + createdAt: expect.toBeValidDate(), + updatedAt: expect.toBeValidDate(), + }); + + // Expected WO+INV userItems: + const { + createdByUserID: WO_B_createdByUserID, + assignedToUserID: WO_B_assignedToUserID, + ...WO_B_fields + } = MOCK_WORK_ORDERS.WO_B; + const { + createdByUserID: INV_A_createdByUserID, + assignedToUserID: INV_A_assignedToUserID, + workOrderID: _INV_A_workOrderID, // null + ...INV_A_fields + } = MOCK_INVOICES.INV_A; + const { + createdByUserID: INV_B_createdByUserID, + assignedToUserID: INV_B_assignedToUserID, + workOrderID: _INV_B_workOrderID, // null + ...INV_B_fields + } = MOCK_INVOICES.INV_B; + const { + createdByUserID: INV_C_createdByUserID, + assignedToUserID: INV_C_assignedToUserID, + workOrderID: INV_C_workOrderID, + ...INV_C_fields + } = MOCK_INVOICES.INV_C; + + // Assert the pre-fetched userItems + expect(responseBody.userItems).toStrictEqual({ + myWorkOrders: { + createdByUser: [ + { + ...WO_B_fields, + __typename: "WorkOrder", + createdBy: { id: WO_B_createdByUserID }, + assignedTo: { id: WO_B_assignedToUserID }, + location: { ...WO_B_fields.location }, // rm Location class prototype + dueDate: WO_B_fields.dueDate.toISOString(), + scheduledDateTime: WO_B_fields.scheduledDateTime.toISOString(), + createdAt: WO_B_fields.createdAt.toISOString(), + updatedAt: WO_B_fields.updatedAt.toISOString(), + }, + ], + assignedToUser: [], + }, + myInvoices: { + createdByUser: [ + { + ...INV_B_fields, + __typename: "Invoice", + createdBy: { id: INV_B_createdByUserID }, + assignedTo: { id: INV_B_assignedToUserID }, + workOrder: null, + createdAt: INV_B_fields.createdAt.toISOString(), + updatedAt: INV_B_fields.updatedAt.toISOString(), + }, + ], + assignedToUser: [ + { + ...INV_A_fields, + __typename: "Invoice", + createdBy: { id: INV_A_createdByUserID }, + assignedTo: { id: INV_A_assignedToUserID }, + workOrder: null, + createdAt: INV_A_fields.createdAt.toISOString(), + updatedAt: INV_A_fields.updatedAt.toISOString(), + }, + { + ...INV_C_fields, + __typename: "Invoice", + createdBy: { id: INV_C_createdByUserID }, + assignedTo: { id: INV_C_assignedToUserID }, + workOrder: { id: INV_C_workOrderID }, + createdAt: INV_C_fields.createdAt.toISOString(), + updatedAt: INV_C_fields.updatedAt.toISOString(), + }, + ], + }, + myContacts: [ + { + __typename: "Contact", + id: MOCK_CONTACTS.CONTACT_C.id, + handle: MOCK_CONTACTS.CONTACT_C.handle, + email: MOCK_USERS.USER_A.email, + phone: MOCK_USERS.USER_A.phone, + profile: MOCK_USERS.USER_A.profile, + createdAt: MOCK_CONTACTS.CONTACT_C.createdAt.toISOString(), + updatedAt: MOCK_CONTACTS.CONTACT_C.updatedAt.toISOString(), + }, ], }); }); @@ -221,7 +435,7 @@ describe("[e2e] Server Requests /api/auth/*", () => { subscription: MOCK_USER_SUBS.SUB_A, }); - // Stub ddbTable.ddbClient.query response in queryUserItems middleware + // Stub ddbTable.ddbClient.query response in queryUserItems function vi.spyOn(ddbTable.ddbClient, "query").mockResolvedValueOnce({ $metadata: {}, Items: [ @@ -230,16 +444,18 @@ describe("[e2e] Server Requests /api/auth/*", () => { UNALIASED_MOCK_USER_SUBS.SUB_A, UNALIASED_MOCK_CONTACTS.CONTACT_A, UNALIASED_MOCK_WORK_ORDERS.WO_A, + UNALIASED_MOCK_WORK_ORDERS.WO_B, UNALIASED_MOCK_INVOICES.INV_A, + UNALIASED_MOCK_INVOICES.INV_B, ], }); - // Stub usersCache.get response in queryUserItems middleware + // Stub usersCache.get response in queryUserItems function vi.spyOn(usersCache, "get").mockReturnValueOnce(MOCK_USERS.USER_B); - // Stub User.updateItem into a no-op in updateExpoPushToken middleware + // Stub User.updateItem into a no-op in update-ExpoPushToken logic vi.spyOn(User, "updateItem").mockResolvedValueOnce({} as any); // Send the request - const { status, body: responseBody } = await request(expressApp) + const { status, body: responseBody } = await request(httpServer) .post("/api/auth/token") .set("Authorization", `Bearer ${mockAuthToken.toString()}`); @@ -249,9 +465,9 @@ describe("[e2e] Server Requests /api/auth/*", () => { assert(isPlainObject(responseBody?.userItems), "response.body.userItems is not present"); // Assert the token payload - const tokenPayload = await AuthToken.validateAndDecodeAuthToken(responseBody.token); + const tokenPayload = await AuthToken.validateAndDecode(responseBody.token); expect(tokenPayload).toStrictEqual({ - id: expect.stringMatching(USER_ID_REGEX), + id: expect.stringMatching(userModelHelpers.id.regex), handle: MOCK_USERS.USER_A.handle, email: MOCK_USERS.USER_A.email, phone: MOCK_USERS.USER_A.phone, @@ -272,50 +488,93 @@ describe("[e2e] Server Requests /api/auth/*", () => { updatedAt: expect.toBeValidDate(), }); - // Expected userItems: + // Expected WO+INV userItems: const { createdByUserID: WO_A_createdByUserID, - assignedToUserID: WO_A_assignedToUserID, // null + assignedToUserID: _WO_A_assignedToUserID, // null ...WO_A_fields } = MOCK_WORK_ORDERS.WO_A; + const { + createdByUserID: WO_B_createdByUserID, + assignedToUserID: WO_B_assignedToUserID, + ...WO_B_fields + } = MOCK_WORK_ORDERS.WO_B; const { createdByUserID: INV_A_createdByUserID, assignedToUserID: INV_A_assignedToUserID, - workOrderID: INV_A_workOrderID, // null + workOrderID: _INV_A_workOrderID, // null ...INV_A_fields } = MOCK_INVOICES.INV_A; + const { + createdByUserID: INV_B_createdByUserID, + assignedToUserID: INV_B_assignedToUserID, + workOrderID: _INV_B_workOrderID, // null + ...INV_B_fields + } = MOCK_INVOICES.INV_B; // Assert the pre-fetched userItems expect(responseBody.userItems).toStrictEqual({ - workOrders: [ - expect.objectContaining({ - ...WO_A_fields, - createdBy: { id: WO_A_createdByUserID }, - assignedTo: null, - createdAt: expect.toBeValidDate(), - updatedAt: expect.toBeValidDate(), - }), - ], - invoices: [ - expect.objectContaining({ - ...INV_A_fields, - createdBy: { id: INV_A_createdByUserID }, - assignedTo: { id: INV_A_assignedToUserID }, - workOrder: INV_A_workOrderID, // null - createdAt: expect.toBeValidDate(), - updatedAt: expect.toBeValidDate(), - }), - ], - contacts: [ - expect.objectContaining({ + myWorkOrders: { + createdByUser: [ + { + ...WO_A_fields, + __typename: "WorkOrder", + createdBy: { id: WO_A_createdByUserID }, + assignedTo: null, + location: { ...WO_A_fields.location }, // rm Location class prototype + createdAt: WO_A_fields.createdAt.toISOString(), + updatedAt: WO_A_fields.updatedAt.toISOString(), + }, + ], + assignedToUser: [ + { + ...WO_B_fields, + __typename: "WorkOrder", + createdBy: { id: WO_B_createdByUserID }, + assignedTo: { id: WO_B_assignedToUserID }, + location: { ...WO_B_fields.location }, // rm Location class prototype + dueDate: WO_B_fields.dueDate.toISOString(), + scheduledDateTime: WO_B_fields.scheduledDateTime.toISOString(), + createdAt: WO_B_fields.createdAt.toISOString(), + updatedAt: WO_B_fields.updatedAt.toISOString(), + }, + ], + }, + myInvoices: { + createdByUser: [ + { + ...INV_A_fields, + __typename: "Invoice", + createdBy: { id: INV_A_createdByUserID }, + assignedTo: { id: INV_A_assignedToUserID }, + workOrder: null, + createdAt: INV_A_fields.createdAt.toISOString(), + updatedAt: INV_A_fields.updatedAt.toISOString(), + }, + ], + assignedToUser: [ + { + ...INV_B_fields, + __typename: "Invoice", + createdBy: { id: INV_B_createdByUserID }, + assignedTo: { id: INV_B_assignedToUserID }, + workOrder: null, + createdAt: INV_B_fields.createdAt.toISOString(), + updatedAt: INV_B_fields.updatedAt.toISOString(), + }, + ], + }, + myContacts: [ + { + __typename: "Contact", id: MOCK_CONTACTS.CONTACT_A.id, handle: MOCK_CONTACTS.CONTACT_A.handle, email: MOCK_USERS.USER_B.email, phone: MOCK_USERS.USER_B.phone, profile: MOCK_USERS.USER_B.profile, - createdAt: expect.toBeValidDate(), - updatedAt: expect.toBeValidDate(), - }), + createdAt: MOCK_CONTACTS.CONTACT_A.createdAt.toISOString(), + updatedAt: MOCK_CONTACTS.CONTACT_A.updatedAt.toISOString(), + }, ], }); }); diff --git a/src/tests/e2e.connectRoutes.test.ts b/src/tests/e2e.connectRoutes.test.ts index f483419f..a916d887 100644 --- a/src/tests/e2e.connectRoutes.test.ts +++ b/src/tests/e2e.connectRoutes.test.ts @@ -1,18 +1,16 @@ import request from "supertest"; -import { expressApp } from "@/expressApp.js"; +import { httpServer, type HttpServerWithCustomStart } from "@/httpServer.js"; import { stripe } from "@/lib/stripe/stripeClient.js"; -import { ENV } from "@/server/env"; +import { AuthToken } from "@/services/AuthService/AuthToken.js"; import { MOCK_USERS, MOCK_USER_SUBS, MOCK_USER_SCAs } from "@/tests/staticMockItems"; -import { AuthToken } from "@/utils/AuthToken.js"; -import type { Server } from "http"; vi.mock("@/apolloServer.js"); describe("[e2e][Server Requests] Routes /api/connect/*", () => { - let server: Server; + let server: HttpServerWithCustomStart; - beforeAll(() => { - server = expressApp.listen(ENV.CONFIG.PORT); + beforeAll(async () => { + server = await httpServer.start(0); }); afterAll(() => { @@ -34,13 +32,13 @@ describe("[e2e][Server Requests] Routes /api/connect/*", () => { vi.spyOn(stripe.accountLinks, "create").mockResolvedValueOnce({ url: mockStripeLink } as any); // Send the request - const { status, body: responseBody } = await request(expressApp) + const { status, body: responseBody } = await request(httpServer) .post("/api/connect/account-link") .set("Authorization", `Bearer ${mockAuthToken.toString()}`) .send({ returnURL: "https://mock-return-url.com" }); // Assert the response - expect(status).toBe(200); + expect(status).toBe(201); expect(responseBody).toStrictEqual({ stripeLink: mockStripeLink }); }); }); @@ -62,12 +60,12 @@ describe("[e2e][Server Requests] Routes /api/connect/*", () => { } as any); // Send the request - const { status, body: responseBody } = await request(expressApp) + const { status, body: responseBody } = await request(httpServer) .get("/api/connect/dashboard-link") .set("Authorization", `Bearer ${mockAuthToken.toString()}`); // Assert the response - expect(status).toBe(200); + expect(status).toBe(201); expect(responseBody).toStrictEqual({ stripeLink: mockStripeLink }); }); }); diff --git a/src/tests/e2e.subscriptionsRoutes.test.ts b/src/tests/e2e.subscriptionsRoutes.test.ts index 2862c79d..9c87d19e 100644 --- a/src/tests/e2e.subscriptionsRoutes.test.ts +++ b/src/tests/e2e.subscriptionsRoutes.test.ts @@ -1,20 +1,19 @@ import { isString } from "@nerdware/ts-type-safety-utils"; import request from "supertest"; -import { expressApp } from "@/expressApp.js"; -import { isValidStripeID } from "@/lib/stripe/isValidStripeID.js"; +import { httpServer, type HttpServerWithCustomStart } from "@/httpServer.js"; +import { isValidStripeID } from "@/lib/stripe/helpers.js"; import { stripe } from "@/lib/stripe/stripeClient.js"; -import { ENV } from "@/server/env"; +import { SUBSCRIPTION_PRICE_NAMES as SUB_PRICE_NAMES } from "@/models/UserSubscription/enumConstants.js"; +import { AuthToken } from "@/services/AuthService/AuthToken.js"; import { MOCK_USERS, MOCK_USER_SUBS, MOCK_USER_SCAs } from "@/tests/staticMockItems"; -import { AuthToken } from "@/utils/AuthToken.js"; -import type { Server } from "http"; vi.mock("@/apolloServer.js"); describe("[e2e][Server Requests] Routes /api/subscriptions/*", () => { - let server: Server; + let server: HttpServerWithCustomStart; - beforeAll(() => { - server = expressApp.listen(ENV.CONFIG.PORT); + beforeAll(async () => { + server = await httpServer.start(0); }); afterAll(() => { @@ -27,30 +26,30 @@ describe("[e2e][Server Requests] Routes /api/subscriptions/*", () => { const mockAuthToken = new AuthToken({ ...MOCK_USERS.USER_A, stripeConnectAccount: MOCK_USER_SCAs.SCA_A, - // No "subscription" field here - User doesn't yet have a sub. + subscription: null, // No subscription yet }); - // Stub stripe.paymentMethods.attach response for findOrCreateStripeSubscription + // Stub stripe.paymentMethods.attach response in CheckoutService.processPayment vi.spyOn(stripe.paymentMethods, "attach").mockResolvedValueOnce(undefined as any); - // Stub stripe.customers.update response for findOrCreateStripeSubscription + // Stub stripe.customers.update response in CheckoutService.processPayment vi.spyOn(stripe.customers, "update").mockResolvedValueOnce({ subscriptions: { data: [] }, } as any); - // Ensure manual ddbDocClient stubs are used for UserSubscription.upsertOne + // Ensure manual ddbDocClient stubs are used for upserting the UserSub vi.mock("@aws-sdk/lib-dynamodb"); // /__mocks__/@aws-sdk/lib-dynamodb.ts // Send the request - const { status, body: responseBody } = await request(expressApp) + const { status, body: responseBody } = await request(httpServer) .post("/api/subscriptions/submit-payment") .set("Authorization", `Bearer ${mockAuthToken.toString()}`) - .send({ selectedSubscription: "ANNUAL", paymentMethodID: "pm_TestTestTest" }); + .send({ selectedSubscription: SUB_PRICE_NAMES.ANNUAL, paymentMethodID: "pm_TestTestTest" }); // Assert the response - expect(status).toBe(200); + expect(status).toBe(201); assert(isString(responseBody?.token), "response.body.token is not present"); // Assert the token payload - const tokenPayload = await AuthToken.validateAndDecodeAuthToken(responseBody.token); + const tokenPayload = await AuthToken.validateAndDecode(responseBody.token); expect(tokenPayload).toStrictEqual({ id: MOCK_USERS.USER_A.id, handle: MOCK_USERS.USER_A.handle, @@ -93,13 +92,13 @@ describe("[e2e][Server Requests] Routes /api/subscriptions/*", () => { } as any); // Send the request - const { status, body: responseBody } = await request(expressApp) + const { status, body: responseBody } = await request(httpServer) .post("/api/subscriptions/customer-portal") .set("Authorization", `Bearer ${mockAuthToken.toString()}`) .send({ returnURL: "https://mock-return-url.com" }); // Assert the response - expect(status).toBe(200); + expect(status).toBe(201); expect(responseBody).toStrictEqual({ stripeLink: mockStripeLink }); }); }); diff --git a/src/tests/setupTests.ts b/src/tests/setupTests.ts index fc3ed0fc..cbcb7f4c 100644 --- a/src/tests/setupTests.ts +++ b/src/tests/setupTests.ts @@ -11,13 +11,16 @@ import dayjs from "dayjs"; * These default mocks satisfy the needs of most unit+int tests, and * are simply overridden where necessary. */ - // MANUAL MOCK LOCATIONS: vi.mock("@/server/env"); // src/server/env/__mocks__/index.ts vi.mock("@/lib/stripe/stripeClient.js"); // src/lib/stripe/__mocks__/stripeClient.ts +vi.mock("@/lib/cache/productsCache.js"); // src/lib/cache/__mocks__/productsCache.ts +vi.mock("@/lib/cache/pricesCache.js"); // src/lib/cache/__mocks__/pricesCache.ts +vi.mock("@/lib/cache/usersCache.js"); // src/lib/cache/__mocks__/usersCache.ts vi.mock("@/models/ddbTable.js"); // src/models/__mocks__/ddbTable.ts vi.mock("@aws-sdk/client-dynamodb"); // __mocks__/@aws-sdk/client-dynamodb.ts vi.mock("@aws-sdk/client-lambda"); // __mocks__/@aws-sdk/client-lambda.ts +vi.mock("@aws-sdk/client-pinpoint"); // __mocks__/@aws-sdk/client-pinpoint.ts vi.mock("@aws-sdk/lib-dynamodb"); // __mocks__/@aws-sdk/lib-dynamodb.ts /** diff --git a/src/tests/staticMockItems/_helpers.ts b/src/tests/staticMockItems/_helpers.ts index 86a3e2e8..40d269de 100644 --- a/src/tests/staticMockItems/_helpers.ts +++ b/src/tests/staticMockItems/_helpers.ts @@ -1,9 +1,6 @@ -import { MOCK_CONTACTS } from "./contacts.js"; -import { MOCK_INVOICES } from "./invoices.js"; import { MOCK_USER_SCAs } from "./userStripeConnectAccounts.js"; import { MOCK_USER_SUBS } from "./userSubscriptions.js"; import { MOCK_USERS } from "./users.js"; -import { MOCK_WORK_ORDERS } from "./workOrders.js"; /** * Factory fn to create mock-item finder functions for use in tests and mocks. The @@ -12,6 +9,7 @@ import { MOCK_WORK_ORDERS } from "./workOrders.js"; * thrown using `errMsg`. */ const getFindMockItemFn = >>( + _label: string, mocks: MocksCollection ) => { const arrayOfMockItems = Object.values(mocks) as Array; @@ -21,6 +19,7 @@ const getFindMockItemFn = { + // logger.test(`Finding mock ${label} item...`); const mockItem = arrayOfMockItems.find(findFn); if (!mockItem) throw new Error(errMsg); return mockItem; @@ -31,16 +30,10 @@ const getFindMockItemFn = ; -/** - * Timestamp UUIDv1 for each date in {@link MOCK_DATES}. - */ -export const MOCK_DATE_v1_UUIDs = { - JAN_1_2020: getUnixTimestampUUID(MOCK_DATES.JAN_1_2020), - JAN_2_2020: getUnixTimestampUUID(MOCK_DATES.JAN_2_2020), - JAN_3_2020: getUnixTimestampUUID(MOCK_DATES.JAN_3_2020), - MAY_1_2020: getUnixTimestampUUID(MOCK_DATES.MAY_1_2020), - MAY_2_2020: getUnixTimestampUUID(MOCK_DATES.MAY_2_2020), - MAY_3_2020: getUnixTimestampUUID(MOCK_DATES.MAY_3_2020), - JAN_1_2021: getUnixTimestampUUID(MOCK_DATES.JAN_1_2021), -} as const satisfies Record; - /** * Unix timestamps (seconds) for each date in {@link MOCK_DATES}. */ diff --git a/src/tests/staticMockItems/invoices.ts b/src/tests/staticMockItems/invoices.ts index c625d74f..e0d90858 100644 --- a/src/tests/staticMockItems/invoices.ts +++ b/src/tests/staticMockItems/invoices.ts @@ -1,15 +1,15 @@ import { invoiceModelHelpers } from "@/models/Invoice/helpers.js"; -import { MOCK_DATES, MOCK_DATE_v1_UUIDs as UUIDs } from "./dates.js"; +import { MOCK_DATES } from "./dates.js"; import { MOCK_USERS } from "./users.js"; import { MOCK_WORK_ORDERS } from "./workOrders.js"; import type { InvoiceItem, UnaliasedInvoiceItem } from "@/models/Invoice"; -const { USER_A, USER_B, USER_C } = MOCK_USERS; +const { USER_A, USER_B } = MOCK_USERS; export const MOCK_INVOICES = { /** [MOCK INV] createdBy: `USER_A`, assignedTo: `USER_B`, status: `"OPEN"` */ INV_A: { - id: invoiceModelHelpers.id.format(USER_A.id, UUIDs.MAY_1_2020), + id: invoiceModelHelpers.id.format(USER_A.id), createdByUserID: USER_A.id, assignedToUserID: USER_B.id, amount: 11111, @@ -19,11 +19,11 @@ export const MOCK_INVOICES = { createdAt: MOCK_DATES.MAY_1_2020, updatedAt: MOCK_DATES.MAY_1_2020, }, - /** [MOCK INV] createdBy: `USER_B`, assignedTo: `USER_C`, status: `"DISPUTED"` */ + /** [MOCK INV] createdBy: `USER_B`, assignedTo: `USER_A`, status: `"DISPUTED"` */ INV_B: { - id: invoiceModelHelpers.id.format(USER_B.id, UUIDs.MAY_2_2020), + id: invoiceModelHelpers.id.format(USER_B.id), createdByUserID: USER_B.id, - assignedToUserID: USER_C.id, + assignedToUserID: USER_A.id, amount: 22222, status: "DISPUTED", workOrderID: null, @@ -33,7 +33,7 @@ export const MOCK_INVOICES = { }, /** [MOCK INV] createdBy: `USER_A`, assignedTo: `USER_B`, status: `"CLOSED"` */ INV_C: { - id: invoiceModelHelpers.id.format(USER_A.id, UUIDs.MAY_3_2020), + id: invoiceModelHelpers.id.format(USER_A.id), createdByUserID: USER_A.id, assignedToUserID: USER_B.id, amount: 33333, diff --git a/src/tests/staticMockItems/passwordResetTokens.ts b/src/tests/staticMockItems/passwordResetTokens.ts new file mode 100644 index 00000000..29da7a5a --- /dev/null +++ b/src/tests/staticMockItems/passwordResetTokens.ts @@ -0,0 +1,49 @@ +import dayjs from "dayjs"; +import { passwordResetTokenModelHelpers } from "@/models/PasswordResetToken/helpers.js"; +import { MOCK_DATES } from "./dates.js"; +import { MOCK_USERS } from "./users.js"; +import type { + PasswordResetTokenItem, + UnaliasedPasswordResetTokenItem, +} from "@/models/PasswordResetToken"; + +const { USER_A, USER_B, USER_C } = MOCK_USERS; + +const TOKEN_STR_A = "a".repeat(96); +const TOKEN_STR_B = "b".repeat(96); +const TOKEN_STR_C = "c".repeat(96); + +export const MOCK_PW_RESET_TOKENS = { + /** */ + TOKEN_A: { + token: TOKEN_STR_A, + sk: passwordResetTokenModelHelpers.sk.format(TOKEN_STR_A), + data: passwordResetTokenModelHelpers.data.format(TOKEN_STR_A), + userID: USER_A.id, + expiresAt: dayjs(MOCK_DATES.JAN_1_2020).add(10, "minutes").unix(), // not expired + }, + /** */ + TOKEN_B: { + token: TOKEN_STR_B, + sk: passwordResetTokenModelHelpers.sk.format(TOKEN_STR_B), + data: passwordResetTokenModelHelpers.data.format(TOKEN_STR_B), + userID: USER_B.id, + expiresAt: dayjs(MOCK_DATES.JAN_2_2020).add(10, "minutes").unix(), // not expired + }, + /** */ + TOKEN_C: { + token: TOKEN_STR_C, + sk: passwordResetTokenModelHelpers.sk.format(TOKEN_STR_C), + data: passwordResetTokenModelHelpers.data.format(TOKEN_STR_C), + userID: USER_C.id, + expiresAt: dayjs(MOCK_DATES.JAN_3_2020).add(20, "minutes").unix(), // expired (TTL = 15 minutes) + }, +} as const satisfies Record; + +/** Unaliased mock PasswordResetTokens for mocking `@aws-sdk/lib-dynamodb` responses. */ +export const UNALIASED_PW_RESET_TOKENS = Object.fromEntries( + Object.entries(MOCK_PW_RESET_TOKENS).map(([key, { token, ...rest }]) => [ + key, + { pk: token, ...rest }, + ]) +) as { [Key in keyof typeof MOCK_PW_RESET_TOKENS]: UnaliasedPasswordResetTokenItem }; diff --git a/src/tests/staticMockItems/userStripeConnectAccounts.ts b/src/tests/staticMockItems/userStripeConnectAccounts.ts index 20fbc4dd..c45a06c2 100644 --- a/src/tests/staticMockItems/userStripeConnectAccounts.ts +++ b/src/tests/staticMockItems/userStripeConnectAccounts.ts @@ -1,4 +1,4 @@ -import { userStripeConnectAccountModelHelpers as scaModelHelpers } from "@/models/UserStripeConnectAccount/helpers.js"; +import { scaModelHelpers } from "@/models/UserStripeConnectAccount/helpers.js"; import { MOCK_DATES } from "./dates.js"; import { MOCK_USERS } from "./users.js"; import type { diff --git a/src/tests/staticMockItems/userSubscriptions.ts b/src/tests/staticMockItems/userSubscriptions.ts index 18a48ce8..4a74bdbc 100644 --- a/src/tests/staticMockItems/userSubscriptions.ts +++ b/src/tests/staticMockItems/userSubscriptions.ts @@ -1,10 +1,10 @@ -import { userSubscriptionModelHelpers as subModelHelpers } from "@/models/UserSubscription/helpers.js"; +import { + UserSubscription, + type UserSubscriptionItem, + type UnaliasedUserSubscriptionItem, +} from "@/models/UserSubscription"; import { MOCK_DATES } from "./dates.js"; import { MOCK_USERS } from "./users.js"; -import type { - UserSubscriptionItem, - UnaliasedUserSubscriptionItem, -} from "@/models/UserSubscription"; const { USER_A, USER_B, USER_C } = MOCK_USERS; @@ -12,7 +12,7 @@ export const MOCK_USER_SUBS = { /** Mock UserSubscription for `USER_A` */ SUB_A: { userID: USER_A.id, - sk: subModelHelpers.sk.format(USER_A.id, MOCK_DATES.JAN_1_2020), + sk: UserSubscription.getFormattedSK(USER_A.id, MOCK_DATES.JAN_1_2020), id: "sub_AAAAAAAAAAAAAAAAAAAAAAAA", status: "active", currentPeriodEnd: MOCK_DATES.JAN_1_2021, @@ -24,7 +24,7 @@ export const MOCK_USER_SUBS = { /** Mock UserSubscription for `USER_B` */ SUB_B: { userID: USER_B.id, - sk: subModelHelpers.sk.format(USER_B.id, MOCK_DATES.JAN_2_2020), + sk: UserSubscription.getFormattedSK(USER_B.id, MOCK_DATES.JAN_2_2020), id: "sub_BBBBBBBBBBBBBBBBBBBBBBBB", status: "active", currentPeriodEnd: MOCK_DATES.JAN_1_2021, @@ -36,7 +36,7 @@ export const MOCK_USER_SUBS = { /** Mock UserSubscription for `USER_C` */ SUB_C: { userID: USER_C.id, - sk: subModelHelpers.sk.format(USER_C.id, MOCK_DATES.JAN_3_2020), + sk: UserSubscription.getFormattedSK(USER_C.id, MOCK_DATES.JAN_3_2020), id: "sub_CCCCCCCCCCCCCCCCCCCCCCCC", status: "active", currentPeriodEnd: MOCK_DATES.JAN_1_2021, diff --git a/src/tests/staticMockItems/users.ts b/src/tests/staticMockItems/users.ts index a38d06e2..06ddb9f8 100644 --- a/src/tests/staticMockItems/users.ts +++ b/src/tests/staticMockItems/users.ts @@ -1,15 +1,23 @@ +import { sanitizePhone } from "@nerdware/ts-string-helpers"; import { userModelHelpers } from "@/models/User/helpers.js"; -import { normalize } from "@/utils/normalize.js"; -import { MOCK_DATES, MOCK_DATE_v1_UUIDs as UUIDs } from "./dates.js"; +import { MOCK_DATES } from "./dates.js"; import type { UserItem, UnaliasedUserItem } from "@/models/User"; +const MOCK_USER_HANDLES = { USER_A: "@user_A", USER_B: "@user_B", USER_C: "@user_C" } as const; + +const MOCK_USER_IDS = { + USER_A: userModelHelpers.id.format(MOCK_USER_HANDLES.USER_A), + USER_B: userModelHelpers.id.format(MOCK_USER_HANDLES.USER_B), + USER_C: userModelHelpers.id.format(MOCK_USER_HANDLES.USER_C), +} as const; + export const MOCK_USERS = { /** Mock User with LOCAL login type. */ USER_A: { - id: userModelHelpers.id.format(UUIDs.JAN_1_2020), - sk: userModelHelpers.sk.format(userModelHelpers.id.format(UUIDs.JAN_1_2020)), - handle: "@user_A", - email: "userA@gmail.com", + id: MOCK_USER_IDS.USER_A, + sk: userModelHelpers.sk.format(MOCK_USER_IDS.USER_A), + handle: MOCK_USER_HANDLES.USER_A, + email: "user_a@gmail.com", phone: "(888) 111-1111", stripeCustomerID: "cus_AAAAAAAAAAAAAAAAAAAAAAAA", expoPushToken: "ExponentPushToken[AAAAAAAAAAAAAAAAAAAAAA]", @@ -29,10 +37,10 @@ export const MOCK_USERS = { }, /** Mock User with GOOGLE_OAUTH login type. */ USER_B: { - id: userModelHelpers.id.format(UUIDs.JAN_2_2020), - sk: userModelHelpers.sk.format(userModelHelpers.id.format(UUIDs.JAN_2_2020)), - handle: "@user_B", - email: "user_B@gmail.com", + id: MOCK_USER_IDS.USER_B, + sk: userModelHelpers.sk.format(MOCK_USER_IDS.USER_B), + handle: MOCK_USER_HANDLES.USER_B, + email: "user_b@gmail.com", phone: "(888) 222-2222", stripeCustomerID: "cus_BBBBBBBBBBBBBBBBBBBBBBBB", expoPushToken: "ExponentPushToken[BBBBBBBBBBBBBBBBBBBBBB]", @@ -52,10 +60,10 @@ export const MOCK_USERS = { }, /** Mock User with LOCAL login type. */ USER_C: { - id: userModelHelpers.id.format(UUIDs.JAN_3_2020), - sk: userModelHelpers.sk.format(userModelHelpers.id.format(UUIDs.JAN_3_2020)), - handle: "@user_C", - email: "user_C@gmail.com", + id: MOCK_USER_IDS.USER_C, + sk: userModelHelpers.sk.format(MOCK_USER_IDS.USER_C), + handle: MOCK_USER_HANDLES.USER_C, + email: "user_c@gmail.com", phone: "(888) 333-3333", stripeCustomerID: "cus_CCCCCCCCCCCCCCCCCCCCCCCC", expoPushToken: null, @@ -82,7 +90,7 @@ export const UNALIASED_MOCK_USERS = Object.fromEntries( { pk: id, data: email, - phone: normalize.phone(phone), + phone: sanitizePhone(phone), ...user, }, ]) diff --git a/src/tests/staticMockItems/workOrders.ts b/src/tests/staticMockItems/workOrders.ts index 575daf6c..07002ba6 100644 --- a/src/tests/staticMockItems/workOrders.ts +++ b/src/tests/staticMockItems/workOrders.ts @@ -1,16 +1,22 @@ +import { sanitizePhone } from "@nerdware/ts-string-helpers"; import { Location } from "@/models/Location"; import { workOrderModelHelpers as woModelHelpers } from "@/models/WorkOrder/helpers.js"; -import { normalize } from "@/utils/normalize.js"; -import { MOCK_DATES, MOCK_DATE_v1_UUIDs as UUIDs } from "./dates.js"; +import { MOCK_DATES } from "./dates.js"; import { MOCK_USERS } from "./users.js"; import type { WorkOrderItem, UnaliasedWorkOrderItem } from "@/models/WorkOrder"; const { USER_A, USER_B, USER_C } = MOCK_USERS; +const WO_IDS = { + WO_A: woModelHelpers.id.format(USER_A.id), + WO_B: woModelHelpers.id.format(USER_B.id), + WO_C: woModelHelpers.id.format(USER_C.id), +} as const; + export const MOCK_WORK_ORDERS = { /** [MOCK WO] createdBy: `USER_A`, assignedTo: `null`, status: `"UNASSIGNED"` */ WO_A: { - id: woModelHelpers.id.format(USER_A.id, UUIDs.MAY_1_2020), + id: WO_IDS.WO_A, createdByUserID: USER_A.id, assignedToUserID: null, status: "UNASSIGNED", @@ -35,7 +41,7 @@ export const MOCK_WORK_ORDERS = { }, /** [MOCK WO] createdBy: `USER_B`, assignedTo: `USER_A`, status: `"ASSIGNED"` */ WO_B: { - id: woModelHelpers.id.format(USER_B.id, UUIDs.MAY_1_2020), + id: WO_IDS.WO_B, createdByUserID: USER_B.id, assignedToUserID: USER_A.id, status: "ASSIGNED", @@ -51,26 +57,17 @@ export const MOCK_WORK_ORDERS = { description: "Do cool things at the Googleplex", checklist: [ { - id: woModelHelpers.checklistItemID.format( - woModelHelpers.id.format(USER_B.id, UUIDs.MAY_1_2020), - UUIDs.MAY_1_2020 - ), + id: woModelHelpers.checklistItemID.format(WO_IDS.WO_B), description: "Do a cool thing", isCompleted: false, }, { - id: woModelHelpers.checklistItemID.format( - woModelHelpers.id.format(USER_B.id, UUIDs.MAY_1_2020), - UUIDs.MAY_2_2020 - ), + id: woModelHelpers.checklistItemID.format(WO_IDS.WO_B), description: "Engineer all the things", isCompleted: false, }, { - id: woModelHelpers.checklistItemID.format( - woModelHelpers.id.format(USER_B.id, UUIDs.MAY_1_2020), - UUIDs.MAY_3_2020 - ), + id: woModelHelpers.checklistItemID.format(WO_IDS.WO_B), description: "Pet a doggo", isCompleted: false, }, @@ -85,7 +82,7 @@ export const MOCK_WORK_ORDERS = { }, /** [MOCK WO] createdBy: `USER_C`, assignedTo: `USER_A`, status: `"COMPLETE"` */ WO_C: { - id: woModelHelpers.id.format(USER_C.id, UUIDs.MAY_1_2020), + id: WO_IDS.WO_C, createdByUserID: USER_C.id, assignedToUserID: USER_A.id, status: "COMPLETE", @@ -123,9 +120,7 @@ export const UNALIASED_MOCK_WORK_ORDERS = Object.fromEntries( sk: id, data: assignedToUserID ?? "UNASSIGNED", location: Location.convertToCompoundString(location), - entryContactPhone: entryContactPhone - ? normalize.phone(entryContactPhone) - : entryContactPhone, + entryContactPhone: entryContactPhone ? sanitizePhone(entryContactPhone) : entryContactPhone, ...workOrder, }, ] diff --git a/src/types/__codegen__/graphql.ts b/src/types/__codegen__/graphql.ts index 8f7cc409..9bb707c0 100644 --- a/src/types/__codegen__/graphql.ts +++ b/src/types/__codegen__/graphql.ts @@ -1,12 +1,11 @@ import type { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql'; -import type { UserItem } from '@/models/User/User.js'; import type { ContactItem } from '@/models/Contact/Contact.js'; -import type { FixitUserCodegenInterface } from '@/graphql/FixitUser/types.js'; +import type { PublicUserFieldsCodegenInterface } from '@/graphql/PublicUserFields/types.js'; import type { InvoiceItem } from '@/models/Invoice/Invoice.js'; import type { WorkOrderItem } from '@/models/WorkOrder/WorkOrder.js'; import type { UserSubscriptionItem } from '@/models/UserSubscription/UserSubscription.js'; import type { UserStripeConnectAccountItem } from '@/models/UserStripeConnectAccount/UserStripeConnectAccount.js'; -import type { ApolloServerResolverContext } from '@/apolloServer.js'; +import type { ApolloServerContext } from '@/apolloServer.js'; export type Maybe = T | null; export type InputMaybe = Maybe; export type Exact = { [K in keyof T]: T[K] }; @@ -29,35 +28,7 @@ export type Scalars = { Email: { input: string; output: string; } }; -export type AuthTokenPayload = { - __typename?: 'AuthTokenPayload'; - createdAt: Scalars['DateTime']['output']; - email: Scalars['String']['output']; - handle: Scalars['String']['output']; - id: Scalars['ID']['output']; - phone: Scalars['String']['output']; - profile: Profile; - stripeConnectAccount: AuthTokenPayloadStripeConnectAccountInfo; - stripeCustomerID: Scalars['String']['output']; - subscription?: Maybe; - updatedAt: Scalars['DateTime']['output']; -}; - -export type AuthTokenPayloadStripeConnectAccountInfo = { - __typename?: 'AuthTokenPayloadStripeConnectAccountInfo'; - chargesEnabled: Scalars['Boolean']['output']; - detailsSubmitted: Scalars['Boolean']['output']; - id: Scalars['ID']['output']; - payoutsEnabled: Scalars['Boolean']['output']; -}; - -export type AuthTokenPayloadSubscriptionInfo = { - __typename?: 'AuthTokenPayloadSubscriptionInfo'; - currentPeriodEnd: Scalars['DateTime']['output']; - id: Scalars['ID']['output']; - status: SubscriptionStatus; -}; - +/** Mutation response for setting a WorkOrder's status to CANCELLED */ export type CancelWorkOrderResponse = DeleteMutationResponse | WorkOrder; export type ChecklistItem = { @@ -69,11 +40,10 @@ export type ChecklistItem = { /** * Contact is a type which is simply a concrete implementation of the publicly - * accessible User fields defined in the FixitUser interface. The Contact type is - * meant to ensure that private User fields are not available to anyone other than - * the User who owns the data. + * accessible user fields defined in the PublicUserFields interface. The Contact + * type represents a user's contact, and is used to manage a user's contacts. */ -export type Contact = FixitUser & { +export type Contact = PublicUserFields & { __typename?: 'Contact'; /** (Immutable) Contact creation timestamp */ createdAt: Scalars['DateTime']['output']; @@ -104,6 +74,7 @@ export type CreateLocationInput = { streetLine2?: InputMaybe; }; +/** Input for creating a new WorkOrder */ export type CreateWorkOrderInput = { assignedTo?: InputMaybe; category?: InputMaybe; @@ -117,59 +88,77 @@ export type CreateWorkOrderInput = { scheduledDateTime?: InputMaybe; }; -export type DeleteMutationResponse = { +/** A mutation response type for delete operations. */ +export type DeleteMutationResponse = MutationResponse & { __typename?: 'DeleteMutationResponse'; + code?: Maybe; + /** The ID of the deleted entity. */ id: Scalars['ID']['output']; - wasDeleted: Scalars['Boolean']['output']; + message?: Maybe; + success: Scalars['Boolean']['output']; }; +/** GraphQLError 'extensions.code' values for client error responses */ +export type GraphQlErrorCode = + /** The GraphQLError 'extensions.code' value for 401-status errors. */ + | 'AUTHENTICATION_REQUIRED' + /** + * The GraphQLError 'extensions.code' value for 400-status errors. + * > This code is an ApolloServer builtin — see [ApolloServerErrorCode][apollo-error-codes]. + * [apollo-error-codes]: https://github.com/apollographql/apollo-server/blob/268687db591fed8293eeded1546ae2f8e6f2b6a7/packages/server/src/errors/index.ts + */ + | 'BAD_USER_INPUT' + /** The GraphQLError 'extensions.code' value for 403-status errors. */ + | 'FORBIDDEN' + /** + * The GraphQLError 'extensions.code' value for 500-status errors. + * > This code is an ApolloServer builtin — see [ApolloServerErrorCode][apollo-error-codes]. + * [apollo-error-codes]: https://github.com/apollographql/apollo-server/blob/268687db591fed8293eeded1546ae2f8e6f2b6a7/packages/server/src/errors/index.ts + */ + | 'INTERNAL_SERVER_ERROR' + /** The GraphQLError 'extensions.code' value for 402-status errors. */ + | 'PAYMENT_REQUIRED' + /** The GraphQLError 'extensions.code' value for 404-status errors. */ + | 'RESOURCE_NOT_FOUND'; + /** - * FixitUser is an interface which defines publicly-accessible User fields. This - * interface has two concrete implementations: Contact, which is simply a concrete - * implementation of the same publicly-available fields, and User, which adds private - * fields which are not accessible to other users. + * GraphQLError custom extensions for client responses. + * See https://www.apollographql.com/docs/apollo-server/data/errors/#custom-errors */ -export type FixitUser = { - createdAt: Scalars['DateTime']['output']; - /** Email address of either a User or Contact */ - email: Scalars['Email']['output']; - /** Public-facing handle identifies users to other users (e.g., '@joe') */ - handle: Scalars['String']['output']; - /** User ID internally identifies individual User accounts */ - id: Scalars['ID']['output']; - /** Phone number of either a User or Contact */ - phone?: Maybe; - /** Profile object of either a User or Contact */ - profile: Profile; - updatedAt: Scalars['DateTime']['output']; +export type GraphQlErrorCustomExtensions = { + __typename?: 'GraphQLErrorCustomExtensions'; + code: GraphQlErrorCode; + http?: Maybe; }; /** - * Generic response-type for mutations which simply returns a "wasSuccessful" boolean. - * This is only ever used as a "last-resort" response-type for mutations which meet all - * of the following criteria: - * 1. The mutation does not perform any database CRUD operations. - * 2. The mutation does not perform any CRUD operations on data maintained by the client-side cache. - * 3. No other response-type is appropriate for the mutation. - * - * Typically the only mutations for which this reponse-type is appropriate are those which - * perform some sort of "side-effect" (e.g. sending an email, sending a text message, etc.). + * GraphQLError custom 'http' extension for providing client error responses + * with traditional HTTP error status codes ('extensions.http.status'). */ -export type GenericSuccessResponse = { - __typename?: 'GenericSuccessResponse'; - wasSuccessful: Scalars['Boolean']['output']; +export type GraphQlErrorCustomHttpExtension = { + __typename?: 'GraphQLErrorCustomHttpExtension'; + /** + * The HTTP status code for the error: + * - 400 'Bad User Input' + * - 401 'Authentication Required' + * - 402 'Payment Required' + * - 403 'Forbidden' + * - 404 'Resource Not Found' + * - 500 'Internal Server Error' + */ + status: Scalars['Int']['output']; }; export type Invoice = { __typename?: 'Invoice'; /** The Invoice amount, represented as an integer which reflects USD centage (i.e., an 'amount' of 1 = $0.01 USD) */ amount: Scalars['Int']['output']; - /** (Immutable) The FixitUser to whom the Invoice was assigned, AKA the Invoice's recipient */ - assignedTo: FixitUser; + /** (Immutable) The User to whom the Invoice was assigned, AKA the Invoice's recipient */ + assignedTo: User; /** (Immutable) Invoice creation timestamp */ createdAt: Scalars['DateTime']['output']; - /** (Immutable) The FixitUser who created/sent the Invoice */ - createdBy: FixitUser; + /** (Immutable) The User who created/sent the Invoice */ + createdBy: User; /** (Immutable) Invoice ID, in the format of 'INV#{createdBy.id}#{unixTimestampUUID(createdAt)}' */ id: Scalars['ID']['output']; /** The Invoice status; this field is controlled by the API and can not be directly edited by Users */ @@ -205,10 +194,9 @@ export type Location = { export type Mutation = { __typename?: 'Mutation'; - _root?: Maybe; cancelWorkOrder: CancelWorkOrderResponse; createContact: Contact; - createInvite: GenericSuccessResponse; + createInvite: MutationResponse; createInvoice: Invoice; createWorkOrder: WorkOrder; deleteContact: DeleteMutationResponse; @@ -282,14 +270,28 @@ export type MutationUpdateWorkOrderArgs = { workOrderID: Scalars['ID']['input']; }; -export type MyInvoicesQueryReturnType = { - __typename?: 'MyInvoicesQueryReturnType'; +/** A generic mutation response type. */ +export type MutationResponse = { + /** + * A code for the mutation response. This may be an HTTP status code, like '200', + * or a GraphQLError code, like 'BAD_USER_INPUT', depending on what makes sense + * for the implementing type. + */ + code?: Maybe; + /** An optional message to provide additional info about the mutation response. */ + message?: Maybe; + /** Whether the mutation was successful. */ + success: Scalars['Boolean']['output']; +}; + +export type MyInvoicesQueryResponse = { + __typename?: 'MyInvoicesQueryResponse'; assignedToUser: Array; createdByUser: Array; }; -export type MyWorkOrdersQueryReturnType = { - __typename?: 'MyWorkOrdersQueryReturnType'; +export type MyWorkOrdersQueryResponse = { + __typename?: 'MyWorkOrdersQueryResponse'; assignedToUser: Array; createdByUser: Array; }; @@ -311,22 +313,37 @@ export type ProfileInput = { photoUrl?: InputMaybe; }; +/** PublicUserFields is an interface which defines publicly-accessible User fields. */ +export type PublicUserFields = { + createdAt: Scalars['DateTime']['output']; + /** User email address */ + email: Scalars['Email']['output']; + /** Public-facing handle identifies users to other users (e.g., '@joe') */ + handle: Scalars['String']['output']; + /** User or Contact ID */ + id: Scalars['ID']['output']; + /** User phone number */ + phone?: Maybe; + /** User Profile object */ + profile: Profile; + updatedAt: Scalars['DateTime']['output']; +}; + export type Query = { __typename?: 'Query'; - _root?: Maybe; contact: Contact; /** * This query returns the public fields of a User whose handle exactly matches the * provided `handle` argument. To search for one or more Users whose handle begins * with or fuzzy-matches a provided string, use `searchForUsersByHandle`. */ - getUserByHandle?: Maybe; + getUserByHandle?: Maybe; invoice: Invoice; myContacts: Array; - myInvoices: MyInvoicesQueryReturnType; + myInvoices: MyInvoicesQueryResponse; myProfile: Profile; mySubscription: UserSubscription; - myWorkOrders: MyWorkOrdersQueryReturnType; + myWorkOrders: MyWorkOrdersQueryResponse; profile: Profile; /** * This query returns a paginated list of Users whose handle begins with the provided @@ -346,8 +363,7 @@ export type Query = { * `nextOffset`. The `data` key will contain the array of matching Users, and `nextOffset` * will be the value of the `offset` argument to be used in a follow-up query. */ - searchForUsersByHandle: Array; - user: User; + searchForUsersByHandle: Array; workOrder: WorkOrder; }; @@ -383,18 +399,33 @@ export type QueryWorkOrderArgs = { workOrderID: Scalars['ID']['input']; }; -export type SubscriptionPriceLabel = +/** Names of the currently available Fixit subscription price-models */ +export type SubscriptionPriceName = + /** The annual subscription price model */ | 'ANNUAL' + /** The monthly subscription price model */ | 'MONTHLY' + /** The trial subscription price model */ | 'TRIAL'; +/** + * The status of a User's Subscription, as provided by Stripe. + * See https://docs.stripe.com/api/subscriptions/object#subscription_object-status + */ export type SubscriptionStatus = + /** The subscription is active and the user has access to the product */ | 'active' + /** The subscription is canceled and the user has lost access to the product */ | 'canceled' + /** The subscription is incomplete and has not yet expired */ | 'incomplete' + /** The subscription is incomplete and has expired */ | 'incomplete_expired' + /** The subscription is past due and the user has lost access to the product */ | 'past_due' + /** The subscription is in a limited trial phase and has access to the product */ | 'trialing' + /** The subscription is unpaid and the user has lost access to the product */ | 'unpaid'; export type UpdateChecklistItemInput = { @@ -411,6 +442,7 @@ export type UpdateLocationInput = { streetLine2?: InputMaybe; }; +/** Input for updating an existing WorkOrder */ export type UpdateWorkOrderInput = { assignedToUserID?: InputMaybe; category?: InputMaybe; @@ -424,19 +456,13 @@ export type UpdateWorkOrderInput = { scheduledDateTime?: InputMaybe; }; -/** - * User is an implementation of the FixitUser interface which includes both the - * publicly-accessible FixitUser/Contact fields as well as private fields which - * are only accessible by the User who owns the data. - */ -export type User = FixitUser & { +/** User is an implementation of the PublicUserFields interface which represents an individual User. */ +export type User = PublicUserFields & { __typename?: 'User'; /** (Immutable) Account creation timestamp */ createdAt: Scalars['DateTime']['output']; - /** (Immutable) User's own email address */ + /** User's own email address */ email: Scalars['Email']['output']; - /** (Mobile-Only) User's Expo push token, used to send push notifications to the User's mobile device */ - expoPushToken?: Maybe; /** (Immutable) Public-facing handle identifies users to other users (e.g., '@joe') */ handle: Scalars['String']['output']; /** (Immutable) User ID internally identifies individual User accounts */ @@ -445,42 +471,51 @@ export type User = FixitUser & { phone?: Maybe; /** User's own Profile object */ profile: Profile; - /** User Stripe Connect Account info */ - stripeConnectAccount?: Maybe; - /** User's Stripe Customer ID (defined and generated by Stripe) */ - stripeCustomerID: Scalars['String']['output']; - /** User Subscription info */ - subscription?: Maybe; /** Timestamp of the most recent account update */ updatedAt: Scalars['DateTime']['output']; }; +/** A user's Stripe Connect Account details */ export type UserStripeConnectAccount = { __typename?: 'UserStripeConnectAccount'; + /** Whether the user's Stripe Connect Account is enabled for charges */ chargesEnabled: Scalars['Boolean']['output']; + /** (Immutable) UserStripeConnectAccount creation timestamp */ createdAt: Scalars['DateTime']['output']; + /** Whether the user has submitted all required details to set up their Stripe Connect Account */ detailsSubmitted: Scalars['Boolean']['output']; + /** (Immutable) The UserStripeConnectAccount's unique ID */ id: Scalars['ID']['output']; + /** Whether the user's Stripe Connect Account is enabled for payouts */ payoutsEnabled: Scalars['Boolean']['output']; + /** Timestamp of the most recent UserStripeConnectAccount update */ updatedAt: Scalars['DateTime']['output']; }; +/** A user's subscription to a Fixit SaaS product */ export type UserSubscription = { __typename?: 'UserSubscription'; + /** (Immutable) UserSubscription creation timestamp */ createdAt: Scalars['DateTime']['output']; + /** The timestamp indicating when the current UserSubscription period ends */ currentPeriodEnd: Scalars['DateTime']['output']; + /** (Immutable) The UserSubscription's unique ID */ id: Scalars['ID']['output']; + /** The UserSubscription's price ID, as provided by Stripe */ priceID: Scalars['String']['output']; + /** The UserSubscription's product ID, as provided by Stripe */ productID: Scalars['String']['output']; + /** The UserSubscription's status, as provided by Stripe */ status: SubscriptionStatus; + /** Timestamp of the most recent UserSubscription update */ updatedAt: Scalars['DateTime']['output']; }; /** A WorkOrder is a request one User submits to another for work to be performed at a location */ export type WorkOrder = { __typename?: 'WorkOrder'; - /** The FixitUser to whom the WorkOrder was assigned, AKA the WorkOrder's recipient */ - assignedTo?: Maybe; + /** The User to whom the WorkOrder was assigned, AKA the WorkOrder's recipient */ + assignedTo?: Maybe; /** The category of work to be performed as part of the WorkOrder */ category?: Maybe; /** The WorkOrder checklist, an array of ChecklistItem objects */ @@ -489,8 +524,8 @@ export type WorkOrder = { contractorNotes?: Maybe; /** (Immutable) WorkOrder creation timestamp */ createdAt: Scalars['DateTime']['output']; - /** (Immutable) The FixitUser who created/sent the WorkOrder */ - createdBy: FixitUser; + /** (Immutable) The User who created/sent the WorkOrder */ + createdBy: User; /** A general description of the work to be performed as part of the WorkOrder */ description?: Maybe; /** Timestamp of the WorkOrder's due date */ @@ -513,34 +548,61 @@ export type WorkOrder = { updatedAt: Scalars['DateTime']['output']; }; +/** The category of work to be performed as part of a WorkOrder */ export type WorkOrderCategory = + /** The WorkOrder involves drywall */ | 'DRYWALL' + /** The WorkOrder involves electrical */ | 'ELECTRICAL' + /** The WorkOrder involves flooring */ | 'FLOORING' + /** The WorkOrder involves general maintenance */ | 'GENERAL' + /** The WorkOrder involves HVAC */ | 'HVAC' + /** The WorkOrder involves landscaping */ | 'LANDSCAPING' + /** The WorkOrder involves masonry */ | 'MASONRY' + /** The WorkOrder involves painting */ | 'PAINTING' + /** The WorkOrder involves paving */ | 'PAVING' + /** The WorkOrder involves pest control */ | 'PEST' + /** The WorkOrder involves plumbing */ | 'PLUMBING' + /** The WorkOrder involves roofing */ | 'ROOFING' + /** The WorkOrder involves trash removal */ | 'TRASH' + /** The WorkOrder involves turnover, i.e., general 'make-ready' tasks for a new tenant or owner */ | 'TURNOVER' + /** The WorkOrder involves windows */ | 'WINDOWS'; +/** The general priority of a WorkOrder */ export type WorkOrderPriority = + /** The WorkOrder is of high priority */ | 'HIGH' + /** The WorkOrder is of low priority */ | 'LOW' + /** The WorkOrder is of normal priority */ | 'NORMAL'; +/** The current status of a WorkOrder */ export type WorkOrderStatus = + /** The WorkOrder has been assigned to a recipient but has not yet been started */ | 'ASSIGNED' + /** The WorkOrder has been cancelled */ | 'CANCELLED' + /** The WorkOrder has been completed */ | 'COMPLETE' + /** The WorkOrder has been deferred to a later date */ | 'DEFERRED' + /** The WorkOrder is in progress */ | 'IN_PROGRESS' + /** The WorkOrder has not yet been assigned to a recipient */ | 'UNASSIGNED'; export type WithIndex = TObject & Record; @@ -613,133 +675,108 @@ export type DirectiveResolverFn> = ResolversObject<{ - CancelWorkOrderResponse: ( DeleteMutationResponse ) | ( WorkOrderItem ); + CancelWorkOrderResponse: ( Partial ) | ( WorkOrderItem ); }>; +/** Mapping of interface types */ +export type ResolversInterfaceTypes> = ResolversObject<{ + MutationResponse: ( Partial ); + PublicUserFields: ( ContactItem ) | ( Partial ); +}>; /** Mapping between all available schema types and the resolvers types */ export type ResolversTypes = ResolversObject<{ - AuthTokenPayload: ResolverTypeWrapper; - AuthTokenPayloadStripeConnectAccountInfo: ResolverTypeWrapper; - AuthTokenPayloadSubscriptionInfo: ResolverTypeWrapper; - Boolean: ResolverTypeWrapper; - CancelWorkOrderResponse: ResolverTypeWrapper['CancelWorkOrderResponse']>; - ChecklistItem: ResolverTypeWrapper; + Boolean: ResolverTypeWrapper>; + CancelWorkOrderResponse: Partial['CancelWorkOrderResponse']>>; + ChecklistItem: ResolverTypeWrapper>; Contact: ResolverTypeWrapper; - CreateChecklistItemInput: CreateChecklistItemInput; - CreateLocationInput: CreateLocationInput; - CreateWorkOrderInput: CreateWorkOrderInput; - DateTime: ResolverTypeWrapper; - DeleteMutationResponse: ResolverTypeWrapper; - Email: ResolverTypeWrapper; - FixitUser: ResolverTypeWrapper; - GenericSuccessResponse: ResolverTypeWrapper; - ID: ResolverTypeWrapper; - Int: ResolverTypeWrapper; + CreateChecklistItemInput: ResolverTypeWrapper>; + CreateLocationInput: ResolverTypeWrapper>; + CreateWorkOrderInput: ResolverTypeWrapper>; + DateTime: ResolverTypeWrapper>; + DeleteMutationResponse: ResolverTypeWrapper>; + Email: ResolverTypeWrapper>; + GraphQLErrorCode: ResolverTypeWrapper>; + GraphQLErrorCustomExtensions: ResolverTypeWrapper>; + GraphQLErrorCustomHttpExtension: ResolverTypeWrapper>; + ID: ResolverTypeWrapper>; + Int: ResolverTypeWrapper>; Invoice: ResolverTypeWrapper; - InvoiceInput: InvoiceInput; - InvoiceStatus: InvoiceStatus; - Location: ResolverTypeWrapper; + InvoiceInput: ResolverTypeWrapper>; + InvoiceStatus: ResolverTypeWrapper>; + Location: ResolverTypeWrapper>; Mutation: ResolverTypeWrapper<{}>; - MyInvoicesQueryReturnType: ResolverTypeWrapper & { assignedToUser: Array, createdByUser: Array }>; - MyWorkOrdersQueryReturnType: ResolverTypeWrapper & { assignedToUser: Array, createdByUser: Array }>; - Profile: ResolverTypeWrapper; - ProfileInput: ProfileInput; + MutationResponse: ResolverTypeWrapper['MutationResponse']>; + MyInvoicesQueryResponse: ResolverTypeWrapper & { assignedToUser: Array, createdByUser: Array }>>; + MyWorkOrdersQueryResponse: ResolverTypeWrapper & { assignedToUser: Array, createdByUser: Array }>>; + Profile: ResolverTypeWrapper>; + ProfileInput: ResolverTypeWrapper>; + PublicUserFields: ResolverTypeWrapper; Query: ResolverTypeWrapper<{}>; - String: ResolverTypeWrapper; - SubscriptionPriceLabel: SubscriptionPriceLabel; - SubscriptionStatus: SubscriptionStatus; - UpdateChecklistItemInput: UpdateChecklistItemInput; - UpdateLocationInput: UpdateLocationInput; - UpdateWorkOrderInput: UpdateWorkOrderInput; - User: ResolverTypeWrapper; + String: ResolverTypeWrapper>; + SubscriptionPriceName: ResolverTypeWrapper>; + SubscriptionStatus: ResolverTypeWrapper>; + UpdateChecklistItemInput: ResolverTypeWrapper>; + UpdateLocationInput: ResolverTypeWrapper>; + UpdateWorkOrderInput: ResolverTypeWrapper>; + User: ResolverTypeWrapper>; UserStripeConnectAccount: ResolverTypeWrapper; UserSubscription: ResolverTypeWrapper; WorkOrder: ResolverTypeWrapper; - WorkOrderCategory: WorkOrderCategory; - WorkOrderPriority: WorkOrderPriority; - WorkOrderStatus: WorkOrderStatus; + WorkOrderCategory: ResolverTypeWrapper>; + WorkOrderPriority: ResolverTypeWrapper>; + WorkOrderStatus: ResolverTypeWrapper>; }>; /** Mapping between all available schema types and the resolvers parents */ export type ResolversParentTypes = ResolversObject<{ - AuthTokenPayload: AuthTokenPayload; - AuthTokenPayloadStripeConnectAccountInfo: AuthTokenPayloadStripeConnectAccountInfo; - AuthTokenPayloadSubscriptionInfo: AuthTokenPayloadSubscriptionInfo; - Boolean: Scalars['Boolean']['output']; - CancelWorkOrderResponse: ResolversUnionTypes['CancelWorkOrderResponse']; - ChecklistItem: ChecklistItem; + Boolean: Partial; + CancelWorkOrderResponse: Partial['CancelWorkOrderResponse']>; + ChecklistItem: Partial; Contact: ContactItem; - CreateChecklistItemInput: CreateChecklistItemInput; - CreateLocationInput: CreateLocationInput; - CreateWorkOrderInput: CreateWorkOrderInput; - DateTime: Scalars['DateTime']['output']; - DeleteMutationResponse: DeleteMutationResponse; - Email: Scalars['Email']['output']; - FixitUser: FixitUserCodegenInterface; - GenericSuccessResponse: GenericSuccessResponse; - ID: Scalars['ID']['output']; - Int: Scalars['Int']['output']; + CreateChecklistItemInput: Partial; + CreateLocationInput: Partial; + CreateWorkOrderInput: Partial; + DateTime: Partial; + DeleteMutationResponse: Partial; + Email: Partial; + GraphQLErrorCustomExtensions: Partial; + GraphQLErrorCustomHttpExtension: Partial; + ID: Partial; + Int: Partial; Invoice: InvoiceItem; - InvoiceInput: InvoiceInput; - Location: Location; + InvoiceInput: Partial; + Location: Partial; Mutation: {}; - MyInvoicesQueryReturnType: Omit & { assignedToUser: Array, createdByUser: Array }; - MyWorkOrdersQueryReturnType: Omit & { assignedToUser: Array, createdByUser: Array }; - Profile: Profile; - ProfileInput: ProfileInput; + MutationResponse: ResolversInterfaceTypes['MutationResponse']; + MyInvoicesQueryResponse: Partial & { assignedToUser: Array, createdByUser: Array }>; + MyWorkOrdersQueryResponse: Partial & { assignedToUser: Array, createdByUser: Array }>; + Profile: Partial; + ProfileInput: Partial; + PublicUserFields: PublicUserFieldsCodegenInterface; Query: {}; - String: Scalars['String']['output']; - UpdateChecklistItemInput: UpdateChecklistItemInput; - UpdateLocationInput: UpdateLocationInput; - UpdateWorkOrderInput: UpdateWorkOrderInput; - User: UserItem; + String: Partial; + UpdateChecklistItemInput: Partial; + UpdateLocationInput: Partial; + UpdateWorkOrderInput: Partial; + User: Partial; UserStripeConnectAccount: UserStripeConnectAccountItem; UserSubscription: UserSubscriptionItem; WorkOrder: WorkOrderItem; }>; -export type AuthTokenPayloadResolvers = ResolversObject<{ - createdAt?: Resolver; - email?: Resolver; - handle?: Resolver; - id?: Resolver; - phone?: Resolver; - profile?: Resolver; - stripeConnectAccount?: Resolver; - stripeCustomerID?: Resolver; - subscription?: Resolver, ParentType, ContextType>; - updatedAt?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}>; - -export type AuthTokenPayloadStripeConnectAccountInfoResolvers = ResolversObject<{ - chargesEnabled?: Resolver; - detailsSubmitted?: Resolver; - id?: Resolver; - payoutsEnabled?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}>; - -export type AuthTokenPayloadSubscriptionInfoResolvers = ResolversObject<{ - currentPeriodEnd?: Resolver; - id?: Resolver; - status?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}>; - -export type CancelWorkOrderResponseResolvers = ResolversObject<{ +export type CancelWorkOrderResponseResolvers = ResolversObject<{ __resolveType: TypeResolveFn<'DeleteMutationResponse' | 'WorkOrder', ParentType, ContextType>; }>; -export type ChecklistItemResolvers = ResolversObject<{ +export type ChecklistItemResolvers = ResolversObject<{ description?: Resolver; id?: Resolver; isCompleted?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type ContactResolvers = ResolversObject<{ +export type ContactResolvers = ResolversObject<{ createdAt?: Resolver; email?: Resolver; handle?: Resolver; @@ -754,9 +791,11 @@ export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig = ResolversObject<{ +export type DeleteMutationResponseResolvers = ResolversObject<{ + code?: Resolver, ParentType, ContextType>; id?: Resolver; - wasDeleted?: Resolver; + message?: Resolver, ParentType, ContextType>; + success?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -764,27 +803,22 @@ export interface EmailScalarConfig extends GraphQLScalarTypeConfig = ResolversObject<{ - __resolveType: TypeResolveFn<'Contact' | 'User', ParentType, ContextType>; - createdAt?: Resolver; - email?: Resolver; - handle?: Resolver; - id?: Resolver; - phone?: Resolver, ParentType, ContextType>; - profile?: Resolver; - updatedAt?: Resolver; +export type GraphQlErrorCustomExtensionsResolvers = ResolversObject<{ + code?: Resolver; + http?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; }>; -export type GenericSuccessResponseResolvers = ResolversObject<{ - wasSuccessful?: Resolver; +export type GraphQlErrorCustomHttpExtensionResolvers = ResolversObject<{ + status?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type InvoiceResolvers = ResolversObject<{ +export type InvoiceResolvers = ResolversObject<{ amount?: Resolver; - assignedTo?: Resolver; + assignedTo?: Resolver; createdAt?: Resolver; - createdBy?: Resolver; + createdBy?: Resolver; id?: Resolver; status?: Resolver; stripePaymentIntentID?: Resolver, ParentType, ContextType>; @@ -793,7 +827,7 @@ export type InvoiceResolvers; }>; -export type LocationResolvers = ResolversObject<{ +export type LocationResolvers = ResolversObject<{ city?: Resolver; country?: Resolver; region?: Resolver; @@ -802,11 +836,10 @@ export type LocationResolvers; }>; -export type MutationResolvers = ResolversObject<{ - _root?: Resolver, ParentType, ContextType>; +export type MutationResolvers = ResolversObject<{ cancelWorkOrder?: Resolver>; createContact?: Resolver>; - createInvite?: Resolver>; + createInvite?: Resolver>; createInvoice?: Resolver>; createWorkOrder?: Resolver>; deleteContact?: Resolver>; @@ -818,19 +851,26 @@ export type MutationResolvers>; }>; -export type MyInvoicesQueryReturnTypeResolvers = ResolversObject<{ +export type MutationResponseResolvers = ResolversObject<{ + __resolveType: TypeResolveFn<'DeleteMutationResponse', ParentType, ContextType>; + code?: Resolver, ParentType, ContextType>; + message?: Resolver, ParentType, ContextType>; + success?: Resolver; +}>; + +export type MyInvoicesQueryResponseResolvers = ResolversObject<{ assignedToUser?: Resolver, ParentType, ContextType>; createdByUser?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; -export type MyWorkOrdersQueryReturnTypeResolvers = ResolversObject<{ +export type MyWorkOrdersQueryResponseResolvers = ResolversObject<{ assignedToUser?: Resolver, ParentType, ContextType>; createdByUser?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; -export type ProfileResolvers = ResolversObject<{ +export type ProfileResolvers = ResolversObject<{ businessName?: Resolver, ParentType, ContextType>; displayName?: Resolver; familyName?: Resolver, ParentType, ContextType>; @@ -839,38 +879,43 @@ export type ProfileResolvers; }>; -export type QueryResolvers = ResolversObject<{ - _root?: Resolver, ParentType, ContextType>; +export type PublicUserFieldsResolvers = ResolversObject<{ + __resolveType: TypeResolveFn<'Contact' | 'User', ParentType, ContextType>; + createdAt?: Resolver; + email?: Resolver; + handle?: Resolver; + id?: Resolver; + phone?: Resolver, ParentType, ContextType>; + profile?: Resolver; + updatedAt?: Resolver; +}>; + +export type QueryResolvers = ResolversObject<{ contact?: Resolver>; - getUserByHandle?: Resolver, ParentType, ContextType, RequireFields>; + getUserByHandle?: Resolver, ParentType, ContextType, RequireFields>; invoice?: Resolver>; myContacts?: Resolver, ParentType, ContextType>; - myInvoices?: Resolver; + myInvoices?: Resolver; myProfile?: Resolver; mySubscription?: Resolver; - myWorkOrders?: Resolver; + myWorkOrders?: Resolver; profile?: Resolver>; - searchForUsersByHandle?: Resolver, ParentType, ContextType, RequireFields>; - user?: Resolver; + searchForUsersByHandle?: Resolver, ParentType, ContextType, RequireFields>; workOrder?: Resolver>; }>; -export type UserResolvers = ResolversObject<{ +export type UserResolvers = ResolversObject<{ createdAt?: Resolver; email?: Resolver; - expoPushToken?: Resolver, ParentType, ContextType>; handle?: Resolver; id?: Resolver; phone?: Resolver, ParentType, ContextType>; profile?: Resolver; - stripeConnectAccount?: Resolver, ParentType, ContextType>; - stripeCustomerID?: Resolver; - subscription?: Resolver, ParentType, ContextType>; updatedAt?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type UserStripeConnectAccountResolvers = ResolversObject<{ +export type UserStripeConnectAccountResolvers = ResolversObject<{ chargesEnabled?: Resolver; createdAt?: Resolver; detailsSubmitted?: Resolver; @@ -880,7 +925,7 @@ export type UserStripeConnectAccountResolvers; }>; -export type UserSubscriptionResolvers = ResolversObject<{ +export type UserSubscriptionResolvers = ResolversObject<{ createdAt?: Resolver; currentPeriodEnd?: Resolver; id?: Resolver; @@ -891,13 +936,13 @@ export type UserSubscriptionResolvers; }>; -export type WorkOrderResolvers = ResolversObject<{ - assignedTo?: Resolver, ParentType, ContextType>; +export type WorkOrderResolvers = ResolversObject<{ + assignedTo?: Resolver, ParentType, ContextType>; category?: Resolver, ParentType, ContextType>; checklist?: Resolver>>, ParentType, ContextType>; contractorNotes?: Resolver, ParentType, ContextType>; createdAt?: Resolver; - createdBy?: Resolver; + createdBy?: Resolver; description?: Resolver, ParentType, ContextType>; dueDate?: Resolver, ParentType, ContextType>; entryContact?: Resolver, ParentType, ContextType>; @@ -911,24 +956,23 @@ export type WorkOrderResolvers; }>; -export type Resolvers = ResolversObject<{ - AuthTokenPayload?: AuthTokenPayloadResolvers; - AuthTokenPayloadStripeConnectAccountInfo?: AuthTokenPayloadStripeConnectAccountInfoResolvers; - AuthTokenPayloadSubscriptionInfo?: AuthTokenPayloadSubscriptionInfoResolvers; +export type Resolvers = ResolversObject<{ CancelWorkOrderResponse?: CancelWorkOrderResponseResolvers; ChecklistItem?: ChecklistItemResolvers; Contact?: ContactResolvers; DateTime?: GraphQLScalarType; DeleteMutationResponse?: DeleteMutationResponseResolvers; Email?: GraphQLScalarType; - FixitUser?: FixitUserResolvers; - GenericSuccessResponse?: GenericSuccessResponseResolvers; + GraphQLErrorCustomExtensions?: GraphQlErrorCustomExtensionsResolvers; + GraphQLErrorCustomHttpExtension?: GraphQlErrorCustomHttpExtensionResolvers; Invoice?: InvoiceResolvers; Location?: LocationResolvers; Mutation?: MutationResolvers; - MyInvoicesQueryReturnType?: MyInvoicesQueryReturnTypeResolvers; - MyWorkOrdersQueryReturnType?: MyWorkOrdersQueryReturnTypeResolvers; + MutationResponse?: MutationResponseResolvers; + MyInvoicesQueryResponse?: MyInvoicesQueryResponseResolvers; + MyWorkOrdersQueryResponse?: MyWorkOrdersQueryResponseResolvers; Profile?: ProfileResolvers; + PublicUserFields?: PublicUserFieldsResolvers; Query?: QueryResolvers; User?: UserResolvers; UserStripeConnectAccount?: UserStripeConnectAccountResolvers; diff --git a/src/types/__codegen__/open-api.ts b/src/types/__codegen__/open-api.ts index 50645754..1b17daf6 100644 --- a/src/types/__codegen__/open-api.ts +++ b/src/types/__codegen__/open-api.ts @@ -1,6 +1,9 @@ /** - * This file was auto-generated by openapi-typescript. - * Do not make direct changes to the file. + * Fixit OpenAPI Schema Types + * + * DO NOT MAKE DIRECT CHANGES TO THIS FILE. + * + * This file was auto-generated using schema version: `2.1.2` */ export interface paths { @@ -13,7 +16,7 @@ export interface paths { }; get?: never; put?: never; - /** Logs CSP violation reports */ + /** Logs CSP violation reports. */ post: operations["CspViolation"]; delete?: never; options?: never; @@ -28,10 +31,10 @@ export interface paths { path?: never; cookie?: never; }; - get?: never; + /** Responds to load balancer healthchecks. */ + get: operations["Healthcheck"]; put?: never; - /** Responds to load balancer healthchecks */ - post: operations["Healthcheck"]; + post?: never; delete?: never; options?: never; head?: never; @@ -47,7 +50,8 @@ export interface paths { }; get?: never; put?: never; - /** Authenticates a user via login credentials */ + /** Authenticates a user for the purposes of accessing protected resources. + * */ post: operations["Login"]; delete?: never; options?: never; @@ -64,7 +68,7 @@ export interface paths { }; get?: never; put?: never; - /** Registers a new user */ + /** Registers a new user. */ post: operations["Register"]; delete?: never; options?: never; @@ -81,7 +85,7 @@ export interface paths { }; get?: never; put?: never; - /** Refreshes a user's auth token */ + /** Refreshes an existing user's auth token. */ post: operations["RefreshToken"]; delete?: never; options?: never; @@ -98,7 +102,7 @@ export interface paths { }; get?: never; put?: never; - /** Processes JSON JWT payloads from GoogleID services (existing users only) */ + /** Authenticates a user via Google OAuth JSON JWT from GoogleID services. */ post: operations["GoogleToken"]; delete?: never; options?: never; @@ -106,6 +110,40 @@ export interface paths { patch?: never; trace?: never; }; + "/auth/password-reset-init": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Initiates the password-reset flow for a user. */ + post: operations["PasswordResetInit"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/password-reset": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Updates the user's password to complete the password-reset flow. */ + post: operations["PasswordReset"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/connect/account-link": { parameters: { query?: never; @@ -115,7 +153,7 @@ export interface paths { }; get?: never; put?: never; - /** Provides a link to the Stripe Connect Account onboarding portal */ + /** Provides a link to the Stripe Connect Account onboarding portal. */ post: operations["ConnectAccountLink"]; delete?: never; options?: never; @@ -130,7 +168,7 @@ export interface paths { path?: never; cookie?: never; }; - /** Provides a link to the Stripe Connect Account dashboard portal */ + /** Provides a link to the Stripe Connect Account dashboard portal. */ get: operations["ConnectDashboardLink"]; put?: never; post?: never; @@ -149,7 +187,7 @@ export interface paths { }; get?: never; put?: never; - /** Checks promo code validity */ + /** Checks the validity of the provided promo code. */ post: operations["CheckPromoCode"]; delete?: never; options?: never; @@ -166,7 +204,7 @@ export interface paths { }; get?: never; put?: never; - /** Provides a link to the Stripe Customer portal */ + /** Provides a link to the Stripe Customer portal. */ post: operations["SubscriptionsCustomerPortal"]; delete?: never; options?: never; @@ -183,7 +221,7 @@ export interface paths { }; get?: never; put?: never; - /** Processes checkout payment information */ + /** Processes checkout payment information. */ post: operations["SubscriptionsSubmitPayment"]; delete?: never; options?: never; @@ -199,7 +237,6 @@ export interface components { handle: components["schemas"]["handle"]; email: components["schemas"]["email"]; phone?: components["schemas"]["phone"]; - profile?: components["schemas"]["UserProfileParams"]; }; LoginParams: components["schemas"]["LoginCredentials"] & components["schemas"]["ExpoPushTokenParam"]; LoginCredentials: components["schemas"]["LoginCredentials.Local"] | components["schemas"]["LoginCredentials.GoogleOAuth"]; @@ -211,25 +248,21 @@ export interface components { GoogleIDTokenField: { googleIDToken: components["schemas"]["googleIDToken"]; }; - /** @description Parameters for a user's profile. */ - UserProfileParams: { - /** @description The user's display name. */ - displayName?: string; - /** @description The user's family name. */ - familyName?: string; - /** @description The user's given name. */ - givenName?: string; - /** @description The user's business name. */ - businessName?: string; - /** @description The user's profile picture URL. */ - photoUrl?: string; - }; /** @description A user's Expo push token, which is used to send push notifications to the user's mobile device. This is an optional parameter which is only sent from mobile clients. * */ ExpoPushTokenParam: { /** @description A user's Expo push token (only available on mobile clients). */ expoPushToken?: string; }; + /** @description The email address of the user initiating a password reset */ + PasswordResetInitParams: { + email: components["schemas"]["email"]; + }; + /** @description A new password and a valid password reset token */ + PasswordResetParams: { + password: components["schemas"]["password"]; + passwordResetToken: components["schemas"]["passwordResetToken"]; + }; /** @description An object which contains an encoded and stringified auth token. */ AuthTokenResponseField: { /** @description An encoded and stringified auth token. */ @@ -238,32 +271,40 @@ export interface components { /** @description An object which contains pre-fetched user items under the key `userItems`. * */ PreFetchedUserItemsResponseField: { - userItems?: components["schemas"]["PreFetchedUserItems"]; + userItems: components["schemas"]["PreFetchedUserItems"]; }; - /** @description A User's pre-fetched WorkOrders, Invoices, and Contacts (used on logins). When fetched - * by the `queryUserItems` middleware (see `src/middleware/auth/queryUserItems.ts`), these - * objects are made available on Express `res.locals` objects under the key `userItems`. - * - * Note: the middleware converts workOrders' and invoices' internal `createdByUserID` and - * `assignedToUserID` fields into `createdBy` and `assignedTo` objects to match the GQL - * schema, but only the `"id"` field can be provided on the createdBy/assignedTo objects - * without fetching additional data on the associated users/contacts from either the db or - * usersCache. The middleware forgoes fetching the data since the client-side Apollo cache - * already handles fetching additional data as needed (_if_ it's needed), and fetching it - * there can delay auth request response times, especially if the authenticating user has - * a large number of items. + /** @description A User's pre-fetched WorkOrders, Invoices, and Contacts, which are written + * into the client's Apollo Client cache on the front-end (used on logins). + * This object's properties correspond to GraphQL queries of the same name. * */ PreFetchedUserItems: { - /** @description The user's work orders. */ - workOrders?: components["schemas"]["WorkOrder"][]; - /** @description The user's invoices. */ - invoices?: components["schemas"]["Invoice"][]; - /** @description The user's contacts. */ - contacts?: components["schemas"]["Contact"][]; + /** @description Pre-fetched `myWorkOrders` query objects for the front-end cache. */ + myWorkOrders: { + /** @description Work orders created by the user. */ + createdByUser: components["schemas"]["WorkOrder"][]; + /** @description Work orders assigned to the user. */ + assignedToUser: components["schemas"]["WorkOrder"][]; + }; + /** @description Pre-fetched `myInvoices` query objects for the front-end cache. */ + myInvoices: { + /** @description Invoices created by the user. */ + createdByUser: components["schemas"]["Invoice"][]; + /** @description Invoices assigned to the user. */ + assignedToUser: components["schemas"]["Invoice"][]; + }; + /** @description Pre-fetched `myContacts` query objects for the front-end cache. */ + myContacts: components["schemas"]["Contact"][]; }; /** @description A pre-fetched Contact object returned from a REST endpoint. */ Contact: { - /** @description The contact's ID */ + /** + * @description The object's GraphQL type name, `"Contact"`, included to facilitate + * writing pre-fetched objects into the front-end's Apollo Client cache. + * + * @enum {string} + */ + __typename: "Contact"; + /** @description The contact's user ID */ id: string; handle: components["schemas"]["handle"]; email: components["schemas"]["email"]; @@ -274,27 +315,30 @@ export interface components { }; /** @description A pre-fetched Invoice object returned from a Fixit REST endpoint. */ Invoice: { + /** + * @description The object's GraphQL type name, `"Invoice"`, included to facilitate + * writing pre-fetched objects into the front-end's Apollo Client cache. + * + * @enum {string} + */ + __typename: "Invoice"; /** @description The invoice's ID. */ id: string; - /** @description The user or contact who created the invoice. */ + /** @description The user who created the invoice. */ createdBy: { - /** @description The ID of the user or contact who created the invoice. */ + /** @description The ID of the user who created the invoice. */ id: string; }; - /** @description The user or contact to whom the invoice is assigned. */ + /** @description The user to whom the invoice is assigned. */ assignedTo: { - /** @description The ID of the user or contact to whom the invoice is assigned. */ + /** @description The ID of the user to whom the invoice is assigned. */ id: string; }; /** @description The Invoice amount, represented as an integer which reflects USD centage * (i.e., an 'amount' of 1 = $0.01 USD). * */ amount: number; - /** - * @description The invoice's status. - * @enum {string} - */ - status: "OPEN" | "CLOSED" | "DISPUTED"; + status: components["schemas"]["InvoiceStatus"]; /** @description The ID of the most recent successful paymentIntent applied to the Invoice, if any. * */ stripePaymentIntentID?: string | null; @@ -308,34 +352,29 @@ export interface components { }; /** @description A pre-fetched WorkOrder object returned from a REST endpoint. */ WorkOrder: { + /** + * @description The object's GraphQL type name, `"WorkOrder"`, included to facilitate + * writing pre-fetched objects into the front-end's Apollo Client cache. + * + * @enum {string} + */ + __typename: "WorkOrder"; /** @description The work order's ID. */ id: string; - /** @description The user or contact who created the work order. */ + /** @description The user who created the work order. */ createdBy: { - /** @description The ID of the user or contact who created the work order. */ + /** @description The ID of the user who created the work order. */ id: string; }; - /** @description The user or contact to whom the work order is assigned. */ + /** @description The user to whom the work order is assigned. */ assignedTo?: { - /** @description The ID of the user or contact to whom the work order is assigned. */ + /** @description The ID of the user to whom the work order is assigned. */ id: string; } | null; - /** - * @description The work order's status. - * @enum {string} - */ - status: "UNASSIGNED" | "ASSIGNED" | "IN_PROGRESS" | "DEFERRED" | "CANCELLED" | "COMPLETE"; - /** - * @description The work order's priority. - * @enum {string} - */ - priority: "LOW" | "NORMAL" | "HIGH"; location: components["schemas"]["Location"]; - /** - * @description The work order's category. - * @enum {string|null} - */ - category?: null | "DRYWALL" | "ELECTRICAL" | "FLOORING" | "GENERAL" | "HVAC" | "LANDSCAPING" | "MASONRY" | "PAINTING" | "PAVING" | "PEST" | "PLUMBING" | "ROOFING" | "TRASH" | "TURNOVER" | "WINDOWS"; + status: components["schemas"]["WorkOrderStatus"]; + priority: components["schemas"]["WorkOrderPriority"]; + category?: components["schemas"]["WorkOrderCategory"]; /** @description The work order's description. */ description?: string | null; /** @description The work order's checklist. */ @@ -419,51 +458,95 @@ export interface components { /** @description A link to a Stripe-provided portal. */ stripeLink: string; }; + /** @description User's Fixit API auth token payload object. */ + AuthTokenPayload: { + /** @description An identifier for the user. */ + id: string; + handle: components["schemas"]["handle"]; + email: components["schemas"]["email"]; + phone: components["schemas"]["phone"]; + profile: components["schemas"]["UserProfile"]; + stripeCustomerID: components["schemas"]["stripeCustomerID"]; + stripeConnectAccount: components["schemas"]["AuthTokenPayloadStripeConnectAccountInfo"]; + subscription: components["schemas"]["AuthTokenPayloadSubscriptionInfo"] | null; + createdAt: components["schemas"]["createdAt"]; + updatedAt: components["schemas"]["updatedAt"]; + }; + /** @description An object within the payload of a user's Fixit API auth token with data + * relating to their current Fixit Subscription status. + * */ + AuthTokenPayloadSubscriptionInfo: { + /** @description An identifier for the subscription. */ + id: string; + status: components["schemas"]["SubscriptionStatus"]; + /** + * Format: date-time + * @description Timestamp indicating the end of the current billing period. + */ + currentPeriodEnd: Date; + }; + /** @description An object within the payload of a user's Fixit API auth token with data + * relating to their current status in the Stripe Connect onboarding flow. + * */ + AuthTokenPayloadStripeConnectAccountInfo: { + /** @description An identifier for the Stripe Connect Account. */ + id: string; + /** @description A boolean indicating whether the user has submitted their details to + * Stripe Connect in the onboarding flow. + * */ + detailsSubmitted: boolean; + /** @description A boolean indicating whether the user has enabled charges on their + * Stripe Connect Account. + * */ + chargesEnabled: boolean; + /** @description A boolean indicating whether the user has enabled payouts on their + * Stripe Connect Account. + * */ + payoutsEnabled: boolean; + }; /** @description A Content Security Policy (CSP) violation report. */ CspViolationReport: { - "csp-report"?: { - /** @description The URI of the protected resource that was violated. - * */ - "document-uri"?: string; - /** @description The URI of the resource that was blocked from loading. - * */ - "blocked-uri"?: string; - /** @description The HTTP status code of the resource that was blocked from loading. - * */ - "status-code"?: number; - /** @description The referrer of the protected resource that was violated. - * */ - referrer?: string; - /** @description The first 40 characters of the inline script, event handler, or style - * that caused the violation. - * */ - "script-sample"?: string; - /** @description The original policy as specified by the Content-Security-Policy header. - * */ - "original-policy"?: string; - /** - * @description Either "enforce" or "report" depending on whether the Content-Security-Policy - * header or the Content-Security-Policy-Report-Only header is used. - * - * @enum {string} - */ - disposition?: "enforce" | "report"; - /** @description The directive whose enforcement was violated (e.g. "default-src 'self'"). - * */ - "violated-directive"?: string; - /** @description The effective directive that was violated (e.g. 'img-src'). - * */ - "effective-directive"?: string; - /** @description The URI of the resource where the violation occurred. - * */ - "source-file"?: string; - /** @description The line number in the resource where the violation occurred. - * */ - "line-number"?: number; - /** @description The column number in the resource where the violation occurred. - * */ - "column-number"?: number; - }; + /** @description The URI of the protected resource that was violated. + * */ + "document-uri"?: string; + /** @description The URI of the resource that was blocked from loading. + * */ + "blocked-uri"?: string; + /** @description The HTTP status code of the resource that was blocked from loading. + * */ + "status-code"?: number; + /** @description The referrer of the protected resource that was violated. + * */ + referrer?: string; + /** @description The first 40 characters of the inline script, event handler, or style + * that caused the violation. + * */ + "script-sample"?: string; + /** @description The original policy as specified by the Content-Security-Policy header. + * */ + "original-policy"?: string; + /** + * @description Either "enforce" or "report" depending on whether the Content-Security-Policy + * header or the Content-Security-Policy-Report-Only header is used. + * + * @enum {string} + */ + disposition?: "enforce" | "report"; + /** @description The directive whose enforcement was violated (e.g. "default-src 'self'"). + * */ + "violated-directive"?: string; + /** @description The effective directive that was violated (e.g. 'img-src'). + * */ + "effective-directive"?: string; + /** @description The URI of the resource where the violation occurred. + * */ + "source-file"?: string; + /** @description The line number in the resource where the violation occurred. + * */ + "line-number"?: number; + /** @description The column number in the resource where the violation occurred. + * */ + "column-number"?: number; }; /** @description An error response object. */ Error: { @@ -485,14 +568,7 @@ export interface components { */ country: string; }; - /** - * @description A Fixit subscription price name — this value corresponds to the [Stripe Price - * "nickname" field](https://stripe.com/docs/api/prices/object#price_object-nickname). - * - * @enum {string} - */ - SubscriptionPriceName: "ANNUAL" | "MONTHLY" | "TRIAL"; - /** @description Parameters for a user's profile. */ + /** @description A user's profile. */ UserProfile: { /** @description The user's display name. */ displayName: string; @@ -505,24 +581,60 @@ export interface components { /** @description The user's profile picture URL. */ photoUrl?: string | null; }; + /** + * @description The Invoice's status. + * @enum {string} + */ + InvoiceStatus: "OPEN" | "CLOSED" | "DISPUTED"; + /** + * @description The Fixit Subscription price name — this value corresponds to the [Stripe Price + * "nickname" field](https://stripe.com/docs/api/prices/object#price_object-nickname). + * + * @enum {string} + */ + SubscriptionPriceName: "ANNUAL" | "MONTHLY" | "TRIAL"; + /** + * @description The Subscription's status, as provided by Stripe. + * See https://docs.stripe.com/api/subscriptions/object#subscription_object-status + * + * @enum {string} + */ + SubscriptionStatus: "active" | "incomplete" | "incomplete_expired" | "trialing" | "past_due" | "canceled" | "unpaid"; + /** + * @description The WorkOrder's category. + * @enum {string|null} + */ + WorkOrderCategory: null | "DRYWALL" | "ELECTRICAL" | "FLOORING" | "GENERAL" | "HVAC" | "LANDSCAPING" | "MASONRY" | "PAINTING" | "PAVING" | "PEST" | "PLUMBING" | "ROOFING" | "TRASH" | "TURNOVER" | "WINDOWS"; + /** + * @description The WorkOrder's priority. + * @enum {string} + */ + WorkOrderPriority: "LOW" | "NORMAL" | "HIGH"; + /** + * @description The WorkOrder's status. + * @enum {string} + */ + WorkOrderStatus: "UNASSIGNED" | "ASSIGNED" | "IN_PROGRESS" | "DEFERRED" | "CANCELLED" | "COMPLETE"; CreatedAt: components["schemas"]["createdAt"]; Email: components["schemas"]["email"]; GoogleIDToken: components["schemas"]["googleIDToken"]; Handle: components["schemas"]["handle"]; Password: components["schemas"]["password"]; + PasswordResetToken: components["schemas"]["passwordResetToken"]; PaymentMethodID: components["schemas"]["paymentMethodID"]; Phone: components["schemas"]["phone"]; PromoCode: components["schemas"]["promoCode"]; ReturnURL: components["schemas"]["returnURL"]; + StripeCustomerID: components["schemas"]["stripeCustomerID"]; UpdatedAt: components["schemas"]["updatedAt"]; /** * Format: email - * @description A user's email address. + * @description User's email address. */ email: string; /** * Format: password - * @description The user's password (auth: local). In order to be valid, a password must meet + * @description User's password (auth: local). In order to be valid, a password must meet * all of the following criteria: * - Contains at least one lowercase letter. * - Contains at least one uppercase letter. @@ -537,7 +649,8 @@ export interface components { email: components["schemas"]["email"]; password: components["schemas"]["password"]; }; - /** @description A base64-encoded JSON JWT from GoogleID services (auth: google-oauth). */ + /** @description Base64URL-encoded JSON JWT from GoogleID services (auth: google-oauth). + * */ googleIDToken: string; /** @description The user's login credentials for google-oauth authentication */ "LoginCredentials.GoogleOAuth": { @@ -546,7 +659,7 @@ export interface components { }; /** * Format: date-time - * @description The timestamp which indicates when the resource was created. + * @description Timestamp which indicates when the resource was created. */ createdAt: Date; /** @@ -554,14 +667,17 @@ export interface components { * @description The timestamp which indicates when the resource was last updated. */ updatedAt: Date; - /** @description A user's Fixit handle. */ + /** @description User's Fixit handle. */ handle: string; - /** @description A user's phone number. Currently this API only supports US phone numbers. All + /** @description User's phone number. Currently this API only supports US phone numbers. All * whitespace, non-numeric characters, and country/calling code prefixes will be * stripped from the phone number upon receipt, so "+1 (555) 555-5555" will be * treated the same as "5555555555". * */ phone: string | null; + /** @description A valid password-reset token for securely resetting a user's password. + * */ + passwordResetToken: string; /** * Format: uri * @description The URL Stripe should redirect the user to upon exiting the Stripe portal. @@ -572,33 +688,47 @@ export interface components { promoCode: string; /** @description The Stripe PaymentMethod ID of the user's chosen payment method. */ paymentMethodID: string; + /** @description User's Stripe Customer ID. */ + stripeCustomerID: string; }; responses: { - "200AuthToken": { + "200AuthTokenAndPreFetchedUserItems": { headers: { [name: string]: unknown; }; content?: never; }; - "200AuthTokenAndPreFetchedUserItems": { + "200CheckPromoCode": { headers: { [name: string]: unknown; }; content?: never; }; - "200AuthTokenAndCheckoutCompletionInfo": { + "200OK": { headers: { [name: string]: unknown; }; content?: never; }; - "200CheckPromoCode": { + "201AuthToken": { headers: { [name: string]: unknown; }; content?: never; }; - "200StripeLink": { + "201AuthTokenAndCheckoutCompletionInfo": { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + "201StripeLink": { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + "204NoContent": { headers: { [name: string]: unknown; }; @@ -646,14 +776,33 @@ export interface components { }; content?: never; }; - /** @description Unexpected response */ + /** @description [Unexpected Response] The server encountered an unexpected condition that + * prevented it from fulfilling the request. This fallback applies if no defined + * response status codes match the response. + * */ "default.UnexpectedResponse": { headers: { [name: string]: unknown; }; content?: never; }; - /** @description OK */ + /** @description [204 No Content][mdn-docs] — Generic success response which does not + * include a response body. + * + * [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + * */ + "204.NoContent": { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description [200 OK][mdn-docs] — Response for a successful authentication request. This + * response includes an authentication token, as well as pre-fetched user items + * to be stored in the client cache. + * + * [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + * */ "200.AuthTokenAndPreFetchedUserItems": { headers: { [name: string]: unknown; @@ -662,7 +811,10 @@ export interface components { "application/json": components["schemas"]["AuthTokenResponseField"] & components["schemas"]["PreFetchedUserItemsResponseField"]; }; }; - /** @description (400) Invalid user input */ + /** @description [400 Bad Request][mdn-docs] — Invalid user input. + * + * [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + * */ "400.InvalidUserInput": { headers: { [name: string]: unknown; @@ -671,7 +823,10 @@ export interface components { "application/json": components["schemas"]["Error"]; }; }; - /** @description (401) Authentication required */ + /** @description [401 Unauthorized][mdn-docs] — Authentication required. + * + * [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + * */ "401.AuthenticationRequired": { headers: { [name: string]: unknown; @@ -680,7 +835,10 @@ export interface components { "application/json": components["schemas"]["Error"]; }; }; - /** @description (5XX) Internal server error */ + /** @description [5XX Internal Server Error][mdn-docs] + * + * [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + * */ "5xx.InternalServerError": { headers: { [name: string]: unknown; @@ -689,8 +847,11 @@ export interface components { "application/json": components["schemas"]["Error"]; }; }; - /** @description OK */ - "200.AuthToken": { + /** @description [201 Created][mdn-docs] — Response for successful user registration. + * + * [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + * */ + "201.AuthToken": { headers: { [name: string]: unknown; }; @@ -698,8 +859,22 @@ export interface components { "application/json": components["schemas"]["AuthTokenResponseField"]; }; }; - /** @description OK */ - "200.StripeLink": { + /** @description [200][mdn-docs] — Generic success response for a request that was processed + * successfully, and which includes a response body. + * + * [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + * */ + "200.OK": { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description [201 Created][mdn-docs] — Response for successful creation of a Stripe link. + * + * [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + * */ + "201.StripeLink": { headers: { [name: string]: unknown; }; @@ -707,7 +882,10 @@ export interface components { "application/json": components["schemas"]["StripeLinkResponseField"]; }; }; - /** @description OK */ + /** @description [200 OK][mdn-docs] — Response for checking a promo code's validity. + * + * [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + * */ "200.CheckPromoCode": { headers: { [name: string]: unknown; @@ -716,8 +894,11 @@ export interface components { "application/json": components["schemas"]["PromoCodeInfoResponseField"]; }; }; - /** @description OK */ - "200.AuthTokenAndCheckoutCompletionInfo": { + /** @description [201 Created][mdn-docs] — Response for successful payment submission. + * + * [mdn-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + * */ + "201.AuthTokenAndCheckoutCompletionInfo": { headers: { [name: string]: unknown; }; @@ -725,7 +906,8 @@ export interface components { "application/json": components["schemas"]["AuthTokenResponseField"] & components["schemas"]["CheckoutCompletionInfoResponseField"]; }; }; - /** @description (402) Payment required */ + /** @description [402 Payment Required] + * */ "402.PaymentRequired": { headers: { [name: string]: unknown; @@ -734,7 +916,7 @@ export interface components { "application/json": components["schemas"]["Error"]; }; }; - /** @description (403) Forbidden — the requesting user is not authorized to perform this action + /** @description [403 Forbidden] The requesting user is not authorized to perform this action. * */ "403.Forbidden": { headers: { @@ -744,7 +926,8 @@ export interface components { "application/json": components["schemas"]["Error"]; }; }; - /** @description (404) Requested resource not found */ + /** @description [404 Not Found] The requested resource could not be found. + * */ "404.ResourceNotFound": { headers: { [name: string]: unknown; @@ -773,6 +956,16 @@ export interface components { "application/json": components["schemas"]["LoginParams"]; }; }; + PasswordResetInitRequest: { + content: { + "application/json": components["schemas"]["PasswordResetInitParams"]; + }; + }; + PasswordResetRequest: { + content: { + "application/json": components["schemas"]["PasswordResetParams"]; + }; + }; RefreshAuthTokenRequest: { content: { "application/json": components["schemas"]["ExpoPushTokenParam"]; @@ -809,13 +1002,7 @@ export interface operations { }; }; responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; + 204: components["responses"]["204.NoContent"]; "4XX": components["responses"]["default.UnexpectedResponse"]; default: components["responses"]["default.UnexpectedResponse"]; }; @@ -829,7 +1016,8 @@ export interface operations { }; requestBody?: never; responses: { - /** @description OK */ + /** @description [200 OK](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200) + * */ 200: { headers: { [name: string]: unknown; @@ -837,7 +1025,7 @@ export interface operations { content: { "application/json": { /** - * @description The string constant "SUCCESS" + * @description The string constant "SUCCESS". * @enum {string} */ message: "SUCCESS"; @@ -873,7 +1061,7 @@ export interface operations { }; requestBody: components["requestBodies"]["UserRegistrationRequest"]; responses: { - 200: components["responses"]["200.AuthToken"]; + 201: components["responses"]["201.AuthToken"]; 400: components["responses"]["400.InvalidUserInput"]; "5XX": components["responses"]["5xx.InternalServerError"]; default: components["responses"]["default.UnexpectedResponse"]; @@ -903,13 +1091,43 @@ export interface operations { }; requestBody: components["requestBodies"]["GoogleTokenRequest"]; responses: { - 200: components["responses"]["200.AuthToken"]; + 200: components["responses"]["200.AuthTokenAndPreFetchedUserItems"]; 400: components["responses"]["400.InvalidUserInput"]; 401: components["responses"]["401.AuthenticationRequired"]; "5XX": components["responses"]["5xx.InternalServerError"]; default: components["responses"]["default.UnexpectedResponse"]; }; }; + PasswordResetInit: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: components["requestBodies"]["PasswordResetInitRequest"]; + responses: { + 200: components["responses"]["200.OK"]; + 400: components["responses"]["400.InvalidUserInput"]; + "5XX": components["responses"]["5xx.InternalServerError"]; + default: components["responses"]["default.UnexpectedResponse"]; + }; + }; + PasswordReset: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: components["requestBodies"]["PasswordResetRequest"]; + responses: { + 200: components["responses"]["200.OK"]; + 400: components["responses"]["400.InvalidUserInput"]; + "5XX": components["responses"]["5xx.InternalServerError"]; + default: components["responses"]["default.UnexpectedResponse"]; + }; + }; ConnectAccountLink: { parameters: { query?: never; @@ -919,7 +1137,7 @@ export interface operations { }; requestBody: components["requestBodies"]["StripeLinkRequest"]; responses: { - 200: components["responses"]["200.StripeLink"]; + 201: components["responses"]["201.StripeLink"]; 400: components["responses"]["400.InvalidUserInput"]; 401: components["responses"]["401.AuthenticationRequired"]; "5XX": components["responses"]["5xx.InternalServerError"]; @@ -935,7 +1153,7 @@ export interface operations { }; requestBody?: never; responses: { - 200: components["responses"]["200.StripeLink"]; + 201: components["responses"]["201.StripeLink"]; 401: components["responses"]["401.AuthenticationRequired"]; "5XX": components["responses"]["5xx.InternalServerError"]; default: components["responses"]["default.UnexpectedResponse"]; @@ -966,7 +1184,7 @@ export interface operations { }; requestBody: components["requestBodies"]["StripeLinkRequest"]; responses: { - 200: components["responses"]["200.StripeLink"]; + 201: components["responses"]["201.StripeLink"]; 400: components["responses"]["400.InvalidUserInput"]; 401: components["responses"]["401.AuthenticationRequired"]; "5XX": components["responses"]["5xx.InternalServerError"]; @@ -990,7 +1208,7 @@ export interface operations { }; }; responses: { - 200: components["responses"]["200.AuthTokenAndCheckoutCompletionInfo"]; + 201: components["responses"]["201.AuthTokenAndCheckoutCompletionInfo"]; 400: components["responses"]["400.InvalidUserInput"]; 401: components["responses"]["401.AuthenticationRequired"]; "5XX": components["responses"]["5xx.InternalServerError"]; diff --git a/src/types/express.ts b/src/types/express.ts deleted file mode 100644 index 9bfa4446..00000000 --- a/src/types/express.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { ParsedGoogleOAuth2IDTokenFields } from "@/lib/googleOAuth2Client"; -import type { UserItem } from "@/models/User/User.js"; -import type { UserSubscriptionItem } from "@/models/UserSubscription"; -import type { FixitApiAuthTokenPayload } from "@/utils/AuthToken.js"; -import type { AllRestApiResponses } from "./open-api.js"; - -/** - * This type contains every Express `res.locals` field used by internal REST middleware. - */ -export type RestApiLocals = { - // LOCALS WHICH STORE DATA FOR DOWNSTREAM MIDDLEWARE: - - /** An AuthToken payload object from an authenticated request's auth token. */ - authenticatedUser?: FixitApiAuthTokenPayload | undefined; - /** Fields provided by a validated Google OAuth2 ID token. */ - googleIDTokenFields?: ParsedGoogleOAuth2IDTokenFields; - /** A User object from the database. */ - user?: UserItem | undefined; - /** A UserSubscription object from the database (e.g., for sub-updating mw). */ - userSubscription?: UserSubscriptionItem | undefined; - - // LOCALS ASSOCIATED WITH A RESPONSE OBJECT: - - /** A stringified and encoded Fixit API {@link AuthToken}. */ - authToken?: AllRestApiResponses["token"] | undefined; - /** A Stripe Customer dashboard link, or Stripe Connect Account flow link. */ - stripeLink?: AllRestApiResponses["stripeLink"] | undefined; - /** A promo code's validity and discount percentage (if valid/applicable). */ - promoCodeInfo?: AllRestApiResponses["promoCodeInfo"] | undefined; - /** A User's pre-fetched WorkOrders, Invoices, and Contacts (used on logins). */ - userItems?: AllRestApiResponses["userItems"] | undefined; - /** An object containing checkout-completion info. */ - checkoutCompletionInfo?: AllRestApiResponses["checkoutCompletionInfo"] | undefined; -}; diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts index feb1f5ee..84a14a9a 100644 --- a/src/types/globals.d.ts +++ b/src/types/globals.d.ts @@ -8,39 +8,47 @@ declare global { * (with the exception of NODE_ENV, which should always be defined). */ interface ProcessEnv { - NODE_ENV?: "development" | "test" | "ci" | "staging" | "production"; - npm_package_version?: string; - PROTOCOL?: string; - DOMAIN?: string; - PORT?: string; - AWS_REGION?: string; - DYNAMODB_TABLE_NAME?: string; - DYNAMODB_ENDPOINT?: string; - JWT_PRIVATE_KEY?: string; - JWT_ALGORITHM?: string; - JWT_ISSUER?: string; - JWT_EXPIRES_IN?: string; - BCRYPT_SALT_ROUNDS?: string; - SENTRY_DSN?: string; - STRIPE_WEBHOOKS_SECRET?: string; - STRIPE_PUBLISHABLE_KEY?: string; - STRIPE_SECRET_KEY?: string; - GOOGLE_OAUTH_CLIENT_ID?: string; - GOOGLE_OAUTH_CLIENT_SECRET?: string; + NODE_ENV?: "development" | "test" | "staging" | "production" | undefined; + npm_package_version?: string | undefined; + // SERVER + PROTOCOL?: string | undefined; + DOMAIN?: string | undefined; + PORT?: string | undefined; + // WEB CLIENT + WEB_CLIENT_URL?: string | undefined; + // AWS + AWS_REGION?: string | undefined; + DYNAMODB_REGION?: string | undefined; + DYNAMODB_TABLE_NAME?: string | undefined; + DYNAMODB_ENDPOINT?: string | undefined; + PINPOINT_PROJECT_ID?: string | undefined; + SES_EMAIL_ADDRESS?: string | undefined; + // AUTH + JWT_PRIVATE_KEY?: string | undefined; + JWT_ALGORITHM?: string | undefined; + JWT_ISSUER?: string | undefined; + JWT_EXPIRES_IN?: string | undefined; + BCRYPT_SALT_ROUNDS?: string | undefined; + UUID_NAMESPACE?: string | undefined; + // SENTRY + SENTRY_DSN?: string | undefined; + // STRIPE + STRIPE_WEBHOOKS_SECRET?: string | undefined; + STRIPE_PUBLISHABLE_KEY?: string | undefined; + STRIPE_SECRET_KEY?: string | undefined; + // GOOGLE + GOOGLE_OAUTH_CLIENT_ID?: string | undefined; + GOOGLE_OAUTH_CLIENT_SECRET?: string | undefined; } } /** - * This declaration makes the following modifications to the `JSON.parse` typedef: - * - * - For the `text: string` argument overload, the `any` return type is replaced with - * {@link JsonValue | type-fest's `JsonValue`} . - * - Add `number` overload, since `JSON.parse(42)` is valid and returns `42`. - * - Add `null` overload, since `JSON.parse(null)` is valid and returns `null`. + * This declaration adds `JSON.parse` overload that replaces `any` with {@link JsonValue}. */ interface JSON { - parse(text: string, reviver?: (this: any, key: string, value: unknown) => unknown): JsonValue; - parse(text: number, reviver?: (this: any, key: string, value: unknown) => unknown): number; - parse(text: null, reviver?: (this: any, key: string, value: unknown) => unknown): null; + parse( + text: JsonValue, + reviver?: (this: typeof JSON, key: string, value: unknown) => unknown + ): JsonValue; } } diff --git a/src/types/graphql.ts b/src/types/graphql.ts index ca225faf..d9e0bd34 100644 --- a/src/types/graphql.ts +++ b/src/types/graphql.ts @@ -1 +1,25 @@ +import type { GraphQLFormattedError } from "graphql"; +import type { Merge, Except } from "type-fest"; +import type { + GraphQlErrorCode, + GraphQlErrorCustomExtensions, + GraphQlErrorCustomHttpExtension, +} from "./__codegen__/graphql.js"; + export * from "./__codegen__/graphql.js"; + +/* For some reason, the gql codegen package is converting "GraphQL" into "GraphQl" +in the generated types. These exports ensure the correct names are available, and +__typename is also removed. */ +export type GraphQLErrorCode = GraphQlErrorCode; +export type GraphQLErrorCustomExtensions = Except; +export type GraphQLErrorCustomHttpExtension = Except; + +/** Alias of {@link GraphQLErrorCustomExtensions}. */ +export type GraphQLErrorExtensions = GraphQLErrorCustomExtensions; + +/** A {@link GraphQLFormattedError} with the app's {@link GraphQLErrorCustomExtensions}. */ +export type GraphQLFormattedErrorWithExtensions = Merge< + GraphQLFormattedError, + { extensions: GraphQLErrorCustomExtensions } +>; diff --git a/src/types/helpers.ts b/src/types/helpers.ts index 11624cea..3ace019a 100644 --- a/src/types/helpers.ts +++ b/src/types/helpers.ts @@ -4,9 +4,11 @@ * built-in generics. */ -/** Intermediate type used by {@link CombineUnionOfObjects}. */ +/** + * Intermediate type used by {@link CombineUnionOfObjects}. + */ type AddMissingFieldsAsPartial< - T extends Record, + T extends object, K extends PropertyKey = T extends unknown ? keyof T : never, > = T extends unknown ? T & Partial, undefined>> : never; @@ -32,6 +34,6 @@ type AddMissingFieldsAsPartial< * } * ``` */ -export type CombineUnionOfObjects> = { +export type CombineUnionOfObjects = { [Key in keyof AddMissingFieldsAsPartial]: AddMissingFieldsAsPartial[Key]; }; diff --git a/src/types/index.ts b/src/types/index.ts deleted file mode 100644 index d4aad06a..00000000 --- a/src/types/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -// REST Codegen Types -export * from "./open-api.js"; -export * from "./express.js"; - -// GraphQL Codegen Types -export * from "./graphql.js"; - -// Other Types -export * from "./helpers.js"; diff --git a/src/types/open-api.ts b/src/types/open-api.ts index c2e2a50b..51f5b6a6 100644 --- a/src/types/open-api.ts +++ b/src/types/open-api.ts @@ -1,14 +1,8 @@ -import type { Simplify, UnionToIntersection } from "type-fest"; -import type { paths, components } from "./__codegen__/open-api.js"; +import type { Simplify, RequiredKeysOf } from "type-fest"; +import type { paths, components, operations } from "./__codegen__/open-api.js"; +import type { CombineUnionOfObjects } from "./helpers.js"; -/** This codegen'd type reflects the `"paths"` of the REST API's OpenAPI schema. */ -export type OpenApiPaths = paths; - -/** This codegen'd type reflects the `"requestBodies"` of the REST API's OpenAPI schema. */ -export type OpenApiRequestBodies = components["requestBodies"]; - -/** This codegen'd type reflects the `"responses"` of the REST API's OpenAPI schema. */ -export type OpenApiResponses = components["responses"]; +export type Paths = Simplify; /** This codegen'd type reflects the `"schemas"` of the REST API's OpenAPI schema. */ export type OpenApiSchemas = components["schemas"]; @@ -16,18 +10,17 @@ export type OpenApiSchemas = components["schemas"]; /////////////////////////////////////////////////////////////////////////////// // REST API Endpoint Paths +/** A union of available REST API endpoints. */ +export type RestApiEndpoint = Simplify; + /** A union of REST API **`POST`** endpoints. */ export type RestApiPOSTendpoint = { - [Path in keyof paths]: paths[Path] extends { post: { requestBody?: BaseJsonContent } } - ? BaseJsonContent extends paths[Path]["post"]["requestBody"] - ? Path - : never - : never; + [Path in keyof paths]: paths[Path] extends { post: Record } ? Path : never; }[keyof paths]; /** A union of REST API **`GET`** endpoints. */ export type RestApiGETendpoint = { - [Path in keyof paths]: paths[Path] extends GETOperationWithJson200Response ? Path : never; + [Path in keyof paths]: paths[Path] extends { get: Record } ? Path : never; }[keyof paths]; /////////////////////////////////////////////////////////////////////////////// @@ -35,50 +28,108 @@ export type RestApiGETendpoint = { /** A map of REST API **`POST`** endpoints to their respective request-body objects. */ export type RestApiRequestBodyByPath = { - [Path in RestApiPOSTendpoint]: Simplify< - NonNullable["content"]["application/json"] - >; + [Path in RestApiPOSTendpoint]: undefined extends paths[Path]["post"]["requestBody"] + ? + | CombineUnionOfObjects> + | undefined + : CombineUnionOfObjects>; }; +/** Extract the type/shape of a POST request's `req.body` () */ +type ExtractPOSTRequestBodyContent< + ReqBody extends paths[RestApiPOSTendpoint]["post"]["requestBody"], + ReqBodyContent = NonNullable["content"], +> = ReqBodyContent extends { "application/json": infer JsonReqBody } + ? JsonReqBody + : ReqBodyContent extends { "application/csp-report": infer CspReqBody } + ? CspReqBody + : never; + /////////////////////////////////////////////////////////////////////////////// // REST API Response Types -/** A map of REST API **`POST`** endpoints to their respective 200-response objects. */ -export type RestApiPOST200ResponseByPath = { - [Path in RestApiPOSTendpoint as paths[Path] extends POSTOperationWithJson200Response - ? Path - : never]: paths[Path] extends POSTOperationWithJson200Response - ? Simplify - : never; +/** The shape of a response object with JSON content. */ +type BaseResponseContentJson = { + content: { "application/json": Record }; }; -/** A map of REST API **`GET`** endpoints to their respective 200-response objects. */ -export type RestApiGET200ResponseByPath = { - [Path in RestApiGETendpoint as paths[Path] extends GETOperationWithJson200Response - ? Path - : never]: paths[Path] extends GETOperationWithJson200Response - ? Simplify - : never; +/** The shape of an OpenAPI operation with success-response status codes. */ +type BaseOpSuccessResponses = operations[keyof operations] & { + responses: { + 200?: BaseResponseContentJson; + 201?: BaseResponseContentJson; + 204?: { content?: never }; + }; +}; + +/** Returns the 200/201/204 success-response object for the provided `Path` (204 content=never). */ +type PathEndpointSuccessResponseContent = + paths[Path]["get"] extends BaseOpSuccessResponses + ? ExtractSuccessResponseContentFromOp + : paths[Path]["post"] extends BaseOpSuccessResponses + ? ExtractSuccessResponseContentFromOp + : never; + +/** Extracts the 200/201/204 success-response object from the provided `Op` (204 content=never). */ +type ExtractSuccessResponseContentFromOp = + Op["responses"][200] extends BaseResponseContentJson + ? Op["responses"][200]["content"]["application/json"] + : Op["responses"][201] extends BaseResponseContentJson + ? Op["responses"][201]["content"]["application/json"] + : Op["responses"][204] extends { content?: never } + ? Op["responses"][204]["content"] + : never; + +/** A map of REST API **`POST`** endpoints to their respective success-response objects. */ +type RestApiPOSTSuccessResponseByPath = { + [Path in RestApiPOSTendpoint]: PathEndpointSuccessResponseContent; +}; + +/** A map of REST API **`GET`** endpoints to their respective success-response objects. */ +type RestApiGETSuccessResponseByPath = { + [Path in RestApiGETendpoint]: PathEndpointSuccessResponseContent; }; /** A map of REST API endpoints to their respective 200-response objects (includes POST and GET endpoints). */ export type RestApiResponseByPath = Simplify< - RestApiPOST200ResponseByPath & RestApiGET200ResponseByPath ->; - -/** An intersection of every 200-response object sent from this API's REST endpoints. */ -export type AllRestApiResponses = Partial< - UnionToIntersection + RestApiPOSTSuccessResponseByPath & RestApiGETSuccessResponseByPath >; -/** The shape of a POST operation with a JSON 200-response. */ -type POSTOperationWithJson200Response = { post: { responses: { 200: BaseJsonContent } } }; +// Response Schema Types: -/** The shape of a GET operation with a JSON 200-response. */ -type GETOperationWithJson200Response = { get: { responses: { 200: BaseJsonContent } } }; +/** The decoded payload of a Fixit API auth token. */ +export type AuthTokenPayload = OpenApiSchemas["AuthTokenPayload"]; +/** Pre-fetched User items. */ +export type PreFetchedUserItems = OpenApiSchemas["PreFetchedUserItems"]; +/** Info returned from the checkout-completion endpoint. */ +export type CheckoutCompletionInfo = OpenApiSchemas["CheckoutCompletionInfo"]; +/** Info returned from the check-promo-code endpoint. */ +export type PromoCodeInfo = OpenApiSchemas["PromoCodeInfo"]; /////////////////////////////////////////////////////////////////////////////// -// Shared Request/Response Base Types +// OpenAPI Path Parameter Types (Path, Query, Header, Cookie) -/** The shape of JSON `content` in OpenAPI request and response objects. */ -type BaseJsonContent = { content: { "application/json": any } }; +type OpenApiSchemaParamType = "path" | "query" | "header" | "cookie"; + +/** The shape of a `parameters` object. */ +type BaseParams = { + parameters: Record>; +}; + +/** Returns the `parameters` of the provided `ParamType` for the provided `Path`. */ +export type RestApiParametersByPath< + Path extends RestApiEndpoint, + ParamType extends OpenApiSchemaParamType, + Endpoint extends Paths[Path] = Paths[Path], + HttpMethod extends EndpointHttpMethod = EndpointHttpMethod, +> = + Endpoint extends BaseParams + ? Endpoint["parameters"]["path"] + : Endpoint[HttpMethod] extends BaseParams + ? Endpoint[HttpMethod] + : Record; + +/** Returns the HTTP method used by the given endpoint. */ +type EndpointHttpMethod = RequiredKeysOf< + Omit +>; diff --git a/src/types/vitest.d.ts b/src/types/vitest.d.ts index fd539dbe..f09368fc 100644 --- a/src/types/vitest.d.ts +++ b/src/types/vitest.d.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars +// eslint-disable-next-line node/no-unpublished-import, @typescript-eslint/no-unused-vars import type { Assertion, AsymmetricMatchersContaining } from "vitest"; /* @@ -6,21 +6,20 @@ import type { Assertion, AsymmetricMatchersContaining } from "vitest"; See docs: https://vitest.dev/guide/extending-matchers.html */ -interface CustomMatchers<_ = any, R = unknown> { +interface CustomMatchers<_ = unknown, R = unknown> { /** Test if the `received` value matches one of the values in the `expected` array. */ toBeOneOf(expected: Array): R; } declare module "vitest" { interface AsymmetricMatchersContaining extends CustomMatchers { - stringMatching(expected: string | RegExp): unknown; /** * Test if the `received` value passes the provided function. * This is an asymmetric version of the existing [`toSatisfy`][toSatisfyLink] matcher. * * [toSatisfyLink]: https://vitest.dev/api/expect.html#tosatisfy */ - toSatisfyFn(matcherFn: (value: any) => boolean): unknown; + toSatisfyFn(matcherFn: (value: MatcherFnArg) => boolean): unknown; toBeValidDate(): unknown; } } diff --git a/src/types/zod.ts b/src/types/zod.ts new file mode 100644 index 00000000..b032c3c9 --- /dev/null +++ b/src/types/zod.ts @@ -0,0 +1,119 @@ +import { SUBSCRIPTION_ENUMS as SUB_ENUMS } from "@/models/UserSubscription/enumConstants.js"; +import { WORK_ORDER_ENUM_CONSTANTS as WO_ENUMS } from "@/models/WorkOrder/enumConstants.js"; +import type { UndefinedOnPartialDeep, Writable } from "type-fest"; +import type { + ZodType, + ZodTypeAny, + ZodTypeDef, + ZodSchema, + ZodObject, + ZodArray, + ZodEnum, + ZodBoolean, + ZodOptional, + ZodNullable, + ZodDefault, + ZodEffects, +} from "zod"; +import type { + SubscriptionPriceName, + SubscriptionStatus, + WorkOrderCategory, + WorkOrderPriority, + WorkOrderStatus, +} from "./graphql.js"; + +// This file contains Zod util types + +/** + * Create a ZodSchema type that matches an existing type T. + * + * > - Use this type for simple `satisfies` checks. + * > - For more complex use cases, use {@link ZodObjectWithShape}. + */ +export type ZodSchemaWithShape> = ZodSchema< + T, + ZodTypeDef, + UndefinedOnPartialDeep +>; + +/** + * Create a ZodObject type that matches an existing type T. + * + * - All keys in T are required. + * - Optionality and nullability are retained. + * - Default values are retained for non-optional keys. + * - Allows ZodEffects (e.g. `.transform()`, `.refine()`). + * + * > - Use this type for more complex zod-related type checks. + * > - For simple `satisfies` checks, use {@link ZodSchemaWithShape}. + */ +export type ZodObjectWithShape> = ZodObject>; + +type ZodShape> = { + [Key in keyof T]-?: FlexibleZodTypeWrapper>; +}; + +type HandleOptionalAndNullable = undefined extends T + ? null extends T + ? ZodOptional>>> + : ZodOptional>> + : null extends T + ? ZodNullable>> + : T extends NonNullable + ? GetZodType + : never; + +type GetZodType = [T] extends [Record] + ? ZodObjectWithShape + : [T] extends [boolean] + ? ZodBoolean + : [T] extends KnownUnionEnums + ? KnownUnionZodEnum<[T]> + : [T] extends [Array] + ? ZodArray> + : ZodType; + +/** + * This type reflects known unions that can be used with ZodEnum. This approach to enum/ZodEnum + * typing is necessary due to TS limitations in regard to deriving a tuple-type from a union. + */ +type KnownUnionEnums = + | [SubscriptionPriceName] + | [SubscriptionStatus] + | [WorkOrderCategory] + | [WorkOrderPriority] + | [WorkOrderStatus]; + +type KnownUnionZodEnum = T extends [SubscriptionPriceName] + ? ZodEnum> + : T extends [SubscriptionStatus] + ? ZodEnum> + : T extends [WorkOrderCategory] + ? ZodEnum> + : T extends [WorkOrderPriority] + ? ZodEnum> + : T extends [WorkOrderStatus] + ? ZodEnum> + : never; + +type FlexibleZodTypeWrapper = + | T + | ZodTypeWithPossibleEffects>; + +type ZodTypeWithPossibleDefault = T | ZodDefault; + +type ZodTypeWithPossibleEffects = + T extends ZodEnum<[string, ...string[]]> + ? ZodEnumWithPossibleEffects + : T | ZodEffects | ZodEffects> | ZodEffects>>; + +type ZodEnumWithPossibleEffects> = + | T + | ZodEffects + | ZodEffects, string, T["options"][number]> + | ZodEffects< + ZodEffects, string, T["options"][number]>, + string, + T["options"][number] + >; diff --git a/src/utils/AuthToken.ts b/src/utils/AuthToken.ts deleted file mode 100644 index d99f1db8..00000000 --- a/src/utils/AuthToken.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { isString } from "@nerdware/ts-type-safety-utils"; -import { signAndEncodeJWT, validateAndDecodeJWT } from "./jwt.js"; -import type { UserItem } from "@/models/User/User.js"; -import type { UserStripeConnectAccountItem } from "@/models/UserStripeConnectAccount/UserStripeConnectAccount.js"; -import type { UserSubscriptionItem } from "@/models/UserSubscription/UserSubscription.js"; -import type { Request } from "express"; -import type jwt from "jsonwebtoken"; -import type { Simplify } from "type-fest"; - -/** - * The AuthToken class is responsible for creating, validating, and decoding JSON - * Web Tokens (JWTs) used for authentication in the Fixit API. - */ -export class AuthToken { - private encodedTokenValue: string; - - /** - * Creates a new AuthToken by signing and encoding a JWT payload using the provided user data. - * @param userData - The user data used to create the JWT payload. - */ - constructor(userData: FixitApiAuthTokenPayload) { - const payload: FixitApiAuthTokenPayload = { - id: userData.id, - handle: userData.handle, - email: userData.email, - phone: userData.phone ?? null, - profile: userData.profile, - stripeCustomerID: userData.stripeCustomerID, - ...(userData.stripeConnectAccount && { - stripeConnectAccount: { - id: userData.stripeConnectAccount.id, - detailsSubmitted: !!userData.stripeConnectAccount.detailsSubmitted, - chargesEnabled: !!userData.stripeConnectAccount.chargesEnabled, - payoutsEnabled: !!userData.stripeConnectAccount.payoutsEnabled, - }, - }), - ...(userData.subscription && { - subscription: { - id: userData.subscription.id, - status: userData.subscription.status, - currentPeriodEnd: userData.subscription.currentPeriodEnd, - }, - }), - createdAt: userData.createdAt, - updatedAt: userData.updatedAt, - }; - - this.encodedTokenValue = signAndEncodeJWT(payload); - } - - /** - * @returns The encoded auth token string. - */ - toString() { - return this.encodedTokenValue; - } - - /** - * Validates and decodes an encoded auth token. - * @param encodedAuthToken - The encoded auth token to validate and decode. - * @returns The decoded auth token payload. - */ - static validateAndDecodeAuthToken = async < - IsSubscriptionDefinitelyPresent extends boolean = false, - IsStripeConnectAccountDefinitelyPresent extends boolean = false, - >( - encodedAuthToken: string - ) => { - const decodedPayload = await validateAndDecodeJWT(encodedAuthToken); - return AuthToken.stripInternalJwtPayloadFields(decodedPayload) as FixitApiAuthTokenPayload< - IsSubscriptionDefinitelyPresent, - IsStripeConnectAccountDefinitelyPresent - >; - }; - - /** - * Validates the "Authorization" header of an incoming request and returns the decoded payload if valid. - * @param request - The incoming request object. - * @returns The decoded auth token payload. - * @throws Error if the token is invalid. - */ - static getValidatedRequestAuthTokenPayload = async < - IsSubscriptionDefinitelyPresent extends boolean = false, - IsStripeConnectAccountDefinitelyPresent extends boolean = false, - >( - request: Request - ) => { - // Get token from "Authorization" header - let token = request.get("Authorization"); - if (!token || !isString(token)) throw new Error("Invalid token"); - - // Remove Bearer from string - if (token.startsWith("Bearer ")) token = token.split(" ")[1]!; - - // Validate the token; if valid, return decoded payload - const authTokenPayload = await this.validateAndDecodeAuthToken< - IsSubscriptionDefinitelyPresent, - IsStripeConnectAccountDefinitelyPresent - >(token); - - return authTokenPayload; - }; - - /** - * Strips internal JWT payload fields from a given payload. - * @param payload - The JWT payload to strip internal fields from. - * @returns The stripped payload. - */ - static stripInternalJwtPayloadFields = < - Payload extends Record = FixitApiAuthTokenPayload, - >( - payload: Payload - ) => { - return Object.fromEntries( - Object.entries(payload).filter( - ([key]) => !["iss", "sub", "aud", "exp", "nbf", "iat", "jti"].includes(key) - ) - ) as Omit; - }; -} - -/** - * The decoded payload of a Fixit API auth token. - */ -export type FixitApiAuthTokenPayload< - IsSubscriptionDefinitelyPresent extends boolean = false, - IsStripeConnectAccountDefinitelyPresent extends boolean = false, -> = Simplify< - AuthTokenUserFields & - (IsStripeConnectAccountDefinitelyPresent extends true - ? { stripeConnectAccount: AuthTokenStripeConnectAccountFields } - : { stripeConnectAccount?: AuthTokenStripeConnectAccountFields }) & - (IsSubscriptionDefinitelyPresent extends true - ? { subscription: AuthTokenSubscriptionFields } - : { subscription?: AuthTokenSubscriptionFields }) ->; - -/** - * The {@link UserItem} fields in the auth token payload. - */ -export type AuthTokenUserFields = Pick< - UserItem, - "id" | "handle" | "email" | "phone" | "profile" | "stripeCustomerID" | "createdAt" | "updatedAt" ->; - -/** - * The {@link UserStripeConnectAccountItem} fields in the auth token payload. - */ -export type AuthTokenStripeConnectAccountFields = Pick< - UserStripeConnectAccountItem, - "id" | "detailsSubmitted" | "chargesEnabled" | "payoutsEnabled" ->; - -/** - * The {@link UserSubscriptionItem} fields in the auth token payload. - */ -export type AuthTokenSubscriptionFields = Pick< - UserSubscriptionItem, - "id" | "status" | "currentPeriodEnd" ->; diff --git a/src/utils/formatters/currency.test.ts b/src/utils/formatters/currency.test.ts index 1f967f4c..0f44f7c3 100644 --- a/src/utils/formatters/currency.test.ts +++ b/src/utils/formatters/currency.test.ts @@ -1,14 +1,15 @@ -import { intToCurrencyStr, intToCurrencyRoundedStr } from "./currency.js"; +import { currencyStrToInt, intToCurrencyStr } from "./currency.js"; describe("formatters/currency", () => { /** * Regex for asserting the error msg thrown from currency formatter * functions when they're called with an invalid value. */ - const INVALID_VALUE_ERR_MSG_REGEX = /invalid value/; + const INVALID_VALUE_ERR_MSG_REGEX = /invalid value/i; describe("intToCurrencyStr()", () => { - // Valid inputs: + // VALID INPUTS (no rounding) + test("returns a string with the correct currency format for a positive integer input", () => { expect(intToCurrencyStr(123456)).toBe("$1,234.56"); }); @@ -24,52 +25,131 @@ describe("formatters/currency", () => { test("returns a string with the correct currency format for a large integer input", () => { expect(intToCurrencyStr(1234567890)).toBe("$12,345,678.90"); }); - // Invalid inputs: - test("throws an error when called with a number which is not a safe integer input", () => { - const INVALID_VALUE_ERR_MSG_REGEX = /invalid value/; + // VALID INPUTS (with rounding) + + test("returns a string with the correct rounded currency format for a positive integer input", () => { + expect(intToCurrencyStr(123456, { shouldRound: true })).toBe("$1,235"); + }); + test("returns a string with the correct rounded currency format for a negative integer input", () => { + expect(intToCurrencyStr(-123456, { shouldRound: true })).toBe("-$1,235"); + }); + test("returns a string with the correct rounded currency format for a zero input", () => { + expect(intToCurrencyStr(0, { shouldRound: true })).toBe("$0"); + }); + test("returns a string with the correct rounded currency format for a small integer input", () => { + expect(intToCurrencyStr(1, { shouldRound: true })).toBe("$0"); + }); + test("returns a string with the correct rounded currency format for a large integer input", () => { + expect(intToCurrencyStr(1234567890, { shouldRound: true })).toBe("$12,345,679"); + }); + + // INVALID INPUTS (no rounding) + + test("throws an error when called with a floating point number", () => { expect(() => intToCurrencyStr(1234.56)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); + }); + test("throws an error when called with NaN", () => { expect(() => intToCurrencyStr(NaN)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); + }); + test("throws an error when called with -NaN", () => { expect(() => intToCurrencyStr(-NaN)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); + }); + test("throws an error when called with Infinity", () => { expect(() => intToCurrencyStr(Infinity)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); + }); + test("throws an error when called with -Infinity", () => { expect(() => intToCurrencyStr(-Infinity)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); }); - test("throws an error when called with a non-number input", () => { + test("throws an error when called with a non-numeric string", () => { expect(() => intToCurrencyStr("abc" as any)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); + }); + test("throws an error when called with null", () => { expect(() => intToCurrencyStr(null as any)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); + }); + test("throws an error when called with undefined", () => { expect(() => intToCurrencyStr(undefined as any)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); }); + + // INVALID INPUTS (with rounding) + + test("throws an error when called with a number which is not a safe integer input", () => { + expect(() => intToCurrencyStr(1234.56, { shouldRound: true })).toThrow( + INVALID_VALUE_ERR_MSG_REGEX + ); + expect(() => intToCurrencyStr(NaN, { shouldRound: true })).toThrow( + INVALID_VALUE_ERR_MSG_REGEX + ); + expect(() => intToCurrencyStr(-NaN, { shouldRound: true })).toThrow( + INVALID_VALUE_ERR_MSG_REGEX + ); + expect(() => intToCurrencyStr(Infinity, { shouldRound: true })).toThrow( + INVALID_VALUE_ERR_MSG_REGEX + ); + expect(() => intToCurrencyStr(-Infinity, { shouldRound: true })).toThrow( + INVALID_VALUE_ERR_MSG_REGEX + ); + }); + test("throws an error when called with a non-number input", () => { + expect(() => intToCurrencyStr("abc" as any, { shouldRound: true })).toThrow( + INVALID_VALUE_ERR_MSG_REGEX + ); + expect(() => intToCurrencyStr(null as any, { shouldRound: true })).toThrow( + INVALID_VALUE_ERR_MSG_REGEX + ); + expect(() => intToCurrencyStr(undefined as any, { shouldRound: true })).toThrow( + INVALID_VALUE_ERR_MSG_REGEX + ); + }); }); - describe("intToCurrencyRoundedStr()", () => { - // Valid inputs: - test("returns a string with the correct currency format for a positive integer input", () => { - expect(intToCurrencyRoundedStr(123456)).toBe("$1,235"); + describe("currencyStrToInt()", () => { + test("returns the correct integer value for a currency string arg with two decimal places", () => { + expect(currencyStrToInt("$ 123.45")).toBe(12345); + expect(currencyStrToInt("$123.45")).toBe(12345); + expect(currencyStrToInt("123.45")).toBe(12345); + + expect(currencyStrToInt("$ 123,456,789.99")).toBe(12345678999); + expect(currencyStrToInt("$123,456,789.99")).toBe(12345678999); + expect(currencyStrToInt("123,456,789.99")).toBe(12345678999); }); - test("returns a string with the correct currency format for a negative integer input", () => { - expect(intToCurrencyRoundedStr(-123456)).toBe("-$1,235"); + + test("returns the correct integer value for a currency string arg without decimal places", () => { + expect(currencyStrToInt("$ 123")).toBe(12300); + expect(currencyStrToInt("$123")).toBe(12300); + expect(currencyStrToInt("123")).toBe(12300); + + expect(currencyStrToInt("$ 123,456,789")).toBe(12345678900); + expect(currencyStrToInt("$123,456,789")).toBe(12345678900); + expect(currencyStrToInt("123,456,789")).toBe(12345678900); }); - test("returns a string with the correct currency format for a zero input", () => { - expect(intToCurrencyRoundedStr(0)).toBe("$0"); + + test("throws an error when called with a non-numeric string arg", () => { + expect(() => currencyStrToInt("abc")).toThrow(INVALID_VALUE_ERR_MSG_REGEX); }); - test("returns a string with the correct currency format for a small integer input", () => { - expect(intToCurrencyRoundedStr(1)).toBe("$0"); + + test("throws an error when the arg is improperly formatted with just 1 decimal place", () => { + expect(() => currencyStrToInt("$1.2")).toThrow(INVALID_VALUE_ERR_MSG_REGEX); }); - test("returns a string with the correct currency format for a large integer input", () => { - expect(intToCurrencyRoundedStr(1234567890)).toBe("$12,345,679"); + + test("throws an error when the arg is improperly formatted with 3 decimal places", () => { + expect(() => currencyStrToInt("$1.234")).toThrow(INVALID_VALUE_ERR_MSG_REGEX); }); - // Invalid inputs: - test("throws an error when called with a number which is not a safe integer input", () => { - expect(() => intToCurrencyRoundedStr(1234.56)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); - expect(() => intToCurrencyRoundedStr(NaN)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); - expect(() => intToCurrencyRoundedStr(-NaN)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); - expect(() => intToCurrencyRoundedStr(Infinity)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); - expect(() => intToCurrencyRoundedStr(-Infinity)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); + + test("throws an error when the arg is improperly formatted with a missing comma", () => { + expect(() => currencyStrToInt("$1234")).toThrow(INVALID_VALUE_ERR_MSG_REGEX); }); - test("throws an error when called with a non-number input", () => { - expect(() => intToCurrencyRoundedStr("abc" as any)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); - expect(() => intToCurrencyRoundedStr(null as any)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); - expect(() => intToCurrencyRoundedStr(undefined as any)).toThrow(INVALID_VALUE_ERR_MSG_REGEX); + + test("throws an error when the arg is improperly formatted with 2 adjacent commas", () => { + expect(() => currencyStrToInt("$1,,234")).toThrow(INVALID_VALUE_ERR_MSG_REGEX); + }); + + test("throws an error when the arg is improperly formatted with 2 spaces after the $", () => { + expect(() => currencyStrToInt("$ 123")).toThrow(INVALID_VALUE_ERR_MSG_REGEX); + }); + + test("throws an error when called with an empty string", () => { + expect(() => currencyStrToInt("")).toThrow(INVALID_VALUE_ERR_MSG_REGEX); }); }); }); diff --git a/src/utils/formatters/currency.ts b/src/utils/formatters/currency.ts index 301c83fd..a7dbd0ed 100644 --- a/src/utils/formatters/currency.ts +++ b/src/utils/formatters/currency.ts @@ -1,45 +1,79 @@ +import { sanitizeNumeric } from "@nerdware/ts-string-helpers"; +import { isSafeInteger, safeJsonStringify } from "@nerdware/ts-type-safety-utils"; import { i18nFormats } from "./i18n.js"; import type { SupportedLocale } from "./i18n.js"; -export type NumberToStringFormatter = (num: number, intlNumberFormat: Intl.NumberFormat) => string; -export type NumberToLocaleStringFormatter = (num: number, locale?: SupportedLocale) => string; - -const validateAndFormatInt: NumberToStringFormatter = (value, intlNumberFormat) => { - if (!Number.isSafeInteger(value)) { - throw new Error(`Currency formatter received an invalid value: ${value}`); - } - - return intlNumberFormat.format(value / 100); -}; - /** - * Converts an integer into a string formatted as a currency amount. - * > The `int` is divided by 100. + * Converts the provided `currencyStr` into an integer. + * + * > - If `currencyStr` has two decimal places, the decimal point is removed. + * > - If `currencyStr` does not have two decimal places, `"00"` is appended to the string. * * ```ts - * formatIntToCurrencyStr(123456); // "$1,234.56" + * currencyStrToInt("$25.99"); // 2599 + * currencyStrToInt("$ 25.00"); // 2500 + * currencyStrToInt("$25"); // 2500 + * currencyStrToInt("$2,500"); // 250000 * ``` * - * @param int - The integer representing a currency amount. - * @returns The currency amount as a formatted string. - * @throws If `int` is not a safe integer. + * @param currencyStr - The currency amount as a string. + * @returns The currency amount as an integer. + * @throws If `currencyStr` is not a valid currency string. */ -export const intToCurrencyStr: NumberToLocaleStringFormatter = (int, locale = "enUS") => { - return validateAndFormatInt(int, i18nFormats[locale].number.currency); +export const currencyStrToInt = (currencyStr: string): number => { + // Validate the input: + if (!/^\$?\s?(\d{1,3}(,\d{3})*(\.\d{2})?)$/.test(currencyStr)) + throw new Error( + `Invalid value: expected a currency string, but received ${safeJsonStringify(currencyStr)}` + ); + + // If the string has two decimal places, remove the decimal point, otherwise append '00'. + const centageStr = /\.\d{2}$/.test(currencyStr) + ? currencyStr.replace(".", "") + : `${currencyStr}00`; + + // Remove all non-digit chars + const centageDigitsStr = sanitizeNumeric(centageStr); + + // Return the integer representation of the string. + return parseInt(centageDigitsStr, 10); }; /** - * Converts an integer into a string formatted as a currency amount, rounded to the nearest dollar. - * > The `int` is divided by 100. + * Converts an integer representing a currency's _minor unit_ into a currency-formatted string. + * + * > The _minor unit_ is the smallest denomination of a currency, e.g., cents for USD. * * ```ts - * formatIntToCurrencyRoundedStr(123456); // "$1,235" + * intToCurrencyStr(123456); // "$1,234.56" + * intToCurrencyStr(123456, // "$1,235" + * { shouldRound: true } + * ); * ``` * - * @param int - The integer representing a currency amount. - * @returns The currency amount as a formatted string, rounded to the nearest dollar. - * @throws If `int` is not a safe integer. + * @param minorCurrencyUnitInteger - The integer representing the currency amount in the minor unit. + * @param options - The options for formatting the currency string. + * @returns The currency-formatted string. + * @throws If the `minorCurrencyUnitInteger` is not a safe integer. */ -export const intToCurrencyRoundedStr: NumberToLocaleStringFormatter = (int, locale = "enUS") => { - return validateAndFormatInt(int, i18nFormats[locale].number.currencyRounded); +export const intToCurrencyStr = ( + minorCurrencyUnitInteger: number, + { + locale = "enUS", + shouldRound = false, + }: { + locale?: SupportedLocale; + shouldRound?: boolean; + } = {} +) => { + if (!isSafeInteger(minorCurrencyUnitInteger)) + throw new Error( + `Invalid value: expected a safe integer, but received ${safeJsonStringify(minorCurrencyUnitInteger)}` + ); + + const intlNumberFormat = shouldRound + ? i18nFormats[locale].number.currencyRounded + : i18nFormats[locale].number.currency; + + return intlNumberFormat.format(minorCurrencyUnitInteger / 100); }; diff --git a/src/utils/formatters/dateTime.ts b/src/utils/formatters/dateTime.ts index 16d70afe..34bfbc05 100644 --- a/src/utils/formatters/dateTime.ts +++ b/src/utils/formatters/dateTime.ts @@ -1,34 +1,4 @@ -import dayjs, { type ConfigType as DayJsInputType } from "dayjs"; - -export type DateTimeFormatter = (dateTime: DayJsInputType, format?: string) => string; - -/** A dictionary of commonly-used DayJS dateTime format strings. */ -export const DAYJS_DATETIME_FORMATS = { - date: "M/D/YY", - shortDate: "MMM Do", - longDate: "dddd, MMMM Do, YYYY", - time: "h:mm A", - dateAndTime: "M/D/YY h:mm a", -} as const; - -const formatDateTime: DateTimeFormatter = (dateTime, format) => { - return dayjs(dateTime).format(format); -}; - -/** Get a DayJS-formatted dateTime string in the format of `"h:mm A"`. */ -export const getTimeStr: DateTimeFormatter = (dateTime) => { - return formatDateTime(dateTime, DAYJS_DATETIME_FORMATS.time); -}; - -/** Get a DayJS-formatted dateTime string in the format of `"M/D/Y"`. */ -export const getDateStr: DateTimeFormatter = (dateTime) => { - return formatDateTime(dateTime, DAYJS_DATETIME_FORMATS.date); -}; - -/** Get a DayJS-formatted dateTime string in the format of `"M/D/Y h:mm a"`. */ -export const getDateAndTimeStr: DateTimeFormatter = (dateTime) => { - return formatDateTime(dateTime, DAYJS_DATETIME_FORMATS.dateAndTime); -}; - -/** Converts a Unix timestamp (<-- _**seconds**_) into a Date object. */ -export const unixToDate = (unix: number) => new Date(unix * 1000); +/** + * Converts a Unix timestamp (<-- _**seconds**_) into a Date object. + */ +export const unixTimestampToDate = (unix: number) => new Date(unix * 1000); diff --git a/src/utils/formatters/index.ts b/src/utils/formatters/index.ts deleted file mode 100644 index 151aaaa8..00000000 --- a/src/utils/formatters/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { intToCurrencyStr, intToCurrencyRoundedStr } from "./currency.js"; -import { getTimeStr, getDateStr, getDateAndTimeStr } from "./dateTime.js"; -import { capitalize, prettifyPhoneNum } from "./strings.js"; - -/** - * A utility object with helper methods for formatting data as "pretty" strings for display. - */ -export const fmt = { - // currency - intToCurrencyStr, - intToCurrencyRoundedStr, - // dateTime - getTimeStr, - getDateStr, - getDateAndTimeStr, - // strings - capitalize, - prettifyPhoneNum, -} as const; diff --git a/src/utils/formatters/phone.test.ts b/src/utils/formatters/phone.test.ts new file mode 100644 index 00000000..d9c32b92 --- /dev/null +++ b/src/utils/formatters/phone.test.ts @@ -0,0 +1,11 @@ +import { prettifyPhoneNumStr } from "./phone.js"; + +describe("formatters/phone", () => { + describe("prettifyPhoneNumStr()", () => { + test(`formats a valid phone number string into a "pretty" format`, () => { + expect(prettifyPhoneNumStr("1234567890")).toBe("(123) 456-7890"); + expect(prettifyPhoneNumStr(" 1234567890 ")).toBe("(123) 456-7890"); + expect(prettifyPhoneNumStr(" +(123)4567890 ")).toBe("(123) 456-7890"); + }); + }); +}); diff --git a/src/utils/formatters/phone.ts b/src/utils/formatters/phone.ts new file mode 100644 index 00000000..50605f8e --- /dev/null +++ b/src/utils/formatters/phone.ts @@ -0,0 +1,11 @@ +import { sanitizePhone } from "@nerdware/ts-string-helpers"; + +/** + * Formats a phone number string into a "pretty" format. + * @param phoneNum The phone number string to format. + * @returns The formatted phone number string. + */ +export const prettifyPhoneNumStr = (phoneNum: string): string => { + const phoneDigits = sanitizePhone(phoneNum); + return `(${phoneDigits.substring(0, 3)}) ${phoneDigits.substring(3, 6)}-${phoneDigits.substring(6, 11)}`; +}; diff --git a/src/utils/formatters/strings.ts b/src/utils/formatters/strings.ts index 5c4a5757..1a21cd77 100644 --- a/src/utils/formatters/strings.ts +++ b/src/utils/formatters/strings.ts @@ -1,49 +1,8 @@ -import { isValidPhone } from "@nerdware/ts-string-helpers"; - -/** - * Formats a phone number string into a "pretty" format. - * - * @param phoneNum The phone number string to format. - * @param shouldValidate Whether or not to validate the input before formatting it (defaults to `true`). - * @returns The formatted phone number string. - * @throws If `shouldValidate` is `true` and the input is invalid. - */ -export const prettifyPhoneNum = (phoneNum: string, shouldValidate: boolean = true) => { - if (shouldValidate && !isValidPhone(phoneNum as unknown)) { - throw new Error(`Phone number formatter received an invalid value: ${phoneNum}`); - } - - return `(${phoneNum.substring(0, 3)}) ${phoneNum.substring(3, 6)}-${phoneNum.substring(6, 11)}`; -}; - /** * Capitalizes the first letter of `str`, and makes all other letters lowercase. - * * @param str The string to capitalize. * @returns The capitalized string. */ export const capitalize = (str: S) => { return `${str.charAt(0).toUpperCase()}${str.slice(1).toLowerCase()}` as Capitalize; }; - -/** - * For each word in the provided string arg, unless the word is included in the - * list below, the first letter is capitalized and the rest are lowercased. The - * common acronyms listed below are entirely uppercased. - * - * Common business name acronyms which are uppercased: - * - LLC - * - INC - * - CO - * - CORP - * - LTD - * - LP - */ -export const prettifyBizName = (rawBizName: string) => { - return rawBizName - .split(" ") - .map((word) => - /^(llc|inc|co|corp|ltd|lp)\.?$/i.test(word) ? word.toUpperCase() : capitalize(word) - ) - .join(" "); -}; diff --git a/src/utils/httpErrors.ts b/src/utils/httpErrors.ts index 0b28a911..9ab762f4 100644 --- a/src/utils/httpErrors.ts +++ b/src/utils/httpErrors.ts @@ -1,130 +1,115 @@ import { ApolloServerErrorCode } from "@apollo/server/errors"; import { getErrorMessage } from "@nerdware/ts-type-safety-utils"; -import { GraphQLError, type GraphQLErrorOptions } from "graphql"; -import deepMerge from "lodash.merge"; import { logger } from "@/utils/logger.js"; -import type { Class } from "type-fest"; +import type { GraphQLErrorCustomExtensions } from "@/types/graphql.js"; +import type { Class, OmitIndexSignature } from "type-fest"; + +type HttpErrorConfig = { + errorName: string; + defaultErrorMsg: string; + gqlErrorExtensions: Required; +}; /** - * Map of HTTP error status codes used by this app to config properties used - * to create Error subclasses for each one. - * - `errName` — The "name" given to associated Error objects. This is prefixed by "Gql" for GraphQLErrors. - * - `defaultErrMsg` — Default error "message". - * - `gqlErrCode` — The `extensions.code` value to use for GraphQLErrors. + * Map of HTTP error status codes used by this app to config properties + * used to create Error subclasses for each one. */ -const HTTP_ERR_STATUS_CODE_CONFIGS = { +export const HTTP_ERROR_CONFIGS: { + readonly 400: HttpErrorConfig; + readonly 401: HttpErrorConfig; + readonly 402: HttpErrorConfig; + readonly 403: HttpErrorConfig; + readonly 404: HttpErrorConfig; + readonly 500: HttpErrorConfig; + readonly [statusCode: number]: HttpErrorConfig; +} = { 400: { - errName: "UserInputError", - defaultErrMsg: "Invalid user input", - gqlErrCode: ApolloServerErrorCode.BAD_USER_INPUT, + errorName: "UserInputError", + defaultErrorMsg: "Invalid user input", + gqlErrorExtensions: { + code: ApolloServerErrorCode.BAD_USER_INPUT, + http: { status: 400 }, + }, }, 401: { - errName: "AuthError", - defaultErrMsg: "Authentication required", - gqlErrCode: "AUTHENTICATION_REQUIRED", + errorName: "AuthError", + defaultErrorMsg: "Authentication required", + gqlErrorExtensions: { + code: "AUTHENTICATION_REQUIRED", + http: { status: 401 }, + }, }, 402: { - errName: "PaymentRequiredError", - defaultErrMsg: "Payment required", - gqlErrCode: "PAYMENT_REQUIRED", + errorName: "PaymentRequiredError", + defaultErrorMsg: "Payment required", + gqlErrorExtensions: { + code: "PAYMENT_REQUIRED", + http: { status: 402 }, + }, }, 403: { - errName: "ForbiddenError", - defaultErrMsg: "Forbidden", - gqlErrCode: "FORBIDDEN", + errorName: "ForbiddenError", + defaultErrorMsg: "Forbidden", + gqlErrorExtensions: { + code: "FORBIDDEN", + http: { status: 403 }, + }, }, 404: { - errName: "NotFoundError", - defaultErrMsg: "Unable to find the requested resource", - gqlErrCode: "RESOURCE_NOT_FOUND", + errorName: "NotFoundError", + defaultErrorMsg: "Unable to find the requested resource", + gqlErrorExtensions: { + code: "RESOURCE_NOT_FOUND", + http: { status: 404 }, + }, }, 500: { - errName: "InternalServerError", - defaultErrMsg: "An unexpected error occurred", - gqlErrCode: ApolloServerErrorCode.INTERNAL_SERVER_ERROR, + errorName: "InternalServerError", + defaultErrorMsg: "An unexpected error occurred", + gqlErrorExtensions: { + code: ApolloServerErrorCode.INTERNAL_SERVER_ERROR, + http: { status: 500 }, + }, }, -} as const satisfies Record; +}; /** - * Factory function for classes which extend Error, or GraphQLError if `gqlErrorCode` - * is provided. + * Factory function for "HttpError" classes which extend {@link Error}. * * @param errorName - The `name` value given to errors created with the returned class. * @param statusCode - The HTTP status code. - * @param gqlErrorCode - The GraphQL error code. - * @returns A class which extends Error that can be used to create Error instances. + * @returns An HttpError class which extends Error that can be used to create Error instances. */ -const createHttpErrorClass = ( - statusCode: keyof typeof HTTP_ERR_STATUS_CODE_CONFIGS, - { gql: isGqlError }: { gql?: IsGqlError } = {} -) => { +const createHttpErrorClass = (statusCode: keyof OmitIndexSignature) => { // Get the status code's relevant configs - const { errName, defaultErrMsg, gqlErrCode } = HTTP_ERR_STATUS_CODE_CONFIGS[statusCode]; + const { errorName, defaultErrorMsg } = HTTP_ERROR_CONFIGS[statusCode]; - const NewClass = - isGqlError === true - ? class GraphqlHttpError extends GraphQLError implements HttpErrorInterface { - override readonly name: string = `Gql${errName}`; // <-- errName is prefixed with "Gql" - readonly statusCode: number = statusCode; - readonly gqlErrorCode: string = gqlErrCode; + const NewClass = class HttpError extends Error implements HttpError { + override readonly name: string = errorName; + readonly statusCode: number = statusCode; - constructor(message?: unknown, gqlErrorOpts?: GraphQLErrorOptions) { - super( - getErrorMessage(message) || defaultErrMsg, - deepMerge( - { - extensions: { - code: gqlErrCode, - http: { - status: statusCode, - }, - }, - }, - gqlErrorOpts ?? {} - ) - ); - // Get stack trace starting where Error was created, omitting the error constructor. - Error.captureStackTrace(this, GraphqlHttpError); - } - } - : class HttpError extends Error implements HttpErrorInterface { - override readonly name: string = errName; - readonly statusCode: number = statusCode; + constructor(message?: unknown) { + super(getErrorMessage(message) || defaultErrorMsg); + Error.captureStackTrace(this, HttpError); + if (statusCode >= 500) logger.error(this); + } + }; - constructor(message?: unknown) { - super(getErrorMessage(message) || defaultErrMsg); - Error.captureStackTrace(this, HttpError); - if (statusCode >= 500) logger.error(this); - } - }; - - return NewClass as unknown as IsGqlError extends true - ? Class - : Class; + return NewClass satisfies Class; }; -/** Interface for custom errors with HTTP status codes. */ -export interface HttpErrorInterface extends Error { +/** Base type for custom errors with HTTP status codes. */ +export type HttpError = Error & { readonly name: string; readonly statusCode: number; -} +}; -// ----------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- // Throwable HTTP error classes: -export const UserInputError = createHttpErrorClass(400, { gql: false }); -export const GqlUserInputError = createHttpErrorClass(400, { gql: true }); - -export const AuthError = createHttpErrorClass(401, { gql: false }); -export const GqlAuthError = createHttpErrorClass(401, { gql: true }); - -export const PaymentRequiredError = createHttpErrorClass(402, { gql: false }); -export const GqlPaymentRequiredError = createHttpErrorClass(402, { gql: true }); - -export const ForbiddenError = createHttpErrorClass(403, { gql: false }); -export const GqlForbiddenError = createHttpErrorClass(403, { gql: true }); - -export const NotFoundError = createHttpErrorClass(404, { gql: false }); - -export const InternalServerError = createHttpErrorClass(500, { gql: false }); -export const GqlInternalServerError = createHttpErrorClass(500, { gql: true }); +export const UserInputError = createHttpErrorClass(400); +export const AuthError = createHttpErrorClass(401); +export const PaymentRequiredError = createHttpErrorClass(402); +export const ForbiddenError = createHttpErrorClass(403); +export const NotFoundError = createHttpErrorClass(404); +export const InternalServerError = createHttpErrorClass(500); diff --git a/src/utils/jwt.test.ts b/src/utils/jwt.test.ts index a63d9a6b..f54d69f0 100644 --- a/src/utils/jwt.test.ts +++ b/src/utils/jwt.test.ts @@ -1,33 +1,33 @@ -import jwt from "jsonwebtoken"; +import jwt, { type Algorithm } from "jsonwebtoken"; import { ENV } from "@/server/env"; -import { signAndEncodeJWT, validateAndDecodeJWT } from "./jwt.js"; +import { JWT } from "./jwt.js"; -/** A valid JWT payload. */ const MOCK_JWT_PAYLOAD = { id: "123" }; +const algorithm = ENV.JWT.ALGORITHM as Algorithm; describe("JWT", () => { - describe("signAndEncodeJWT()", () => { + describe("JWT.signAndEncode()", () => { test("returns a valid signed JWT when called with a valid payload arg", async () => { - const token = signAndEncodeJWT(MOCK_JWT_PAYLOAD); - const result = await validateAndDecodeJWT(token); + const token = JWT.signAndEncode(MOCK_JWT_PAYLOAD); + const result = await JWT.validateAndDecode(token); expect(result).toStrictEqual(expect.objectContaining(MOCK_JWT_PAYLOAD)); }); }); - describe("validateAndDecodeJWT()", () => { + describe("JWT.validateAndDecode()", () => { test("returns a decoded payload when called with a valid token arg", async () => { const token = jwt.sign(MOCK_JWT_PAYLOAD, ENV.JWT.PRIVATE_KEY, { audience: ENV.CONFIG.API_BASE_URL, issuer: ENV.JWT.ISSUER, - algorithm: ENV.JWT.ALGORITHM, + algorithm, expiresIn: "5m", }); - const result = await validateAndDecodeJWT(token); + const result = await JWT.validateAndDecode(token); expect(result).toStrictEqual(expect.objectContaining(MOCK_JWT_PAYLOAD)); }); test("throws an error when called with an invalid token arg", async () => { - await expect(validateAndDecodeJWT("invalid_token")).rejects.toThrow( + await expect(JWT.validateAndDecode("invalid_token")).rejects.toThrow( "Signature verification failed" ); }); @@ -36,20 +36,20 @@ describe("JWT", () => { const token = jwt.sign(MOCK_JWT_PAYLOAD, "invalid_private_key", { audience: ENV.CONFIG.API_BASE_URL, issuer: ENV.JWT.ISSUER, - algorithm: ENV.JWT.ALGORITHM, + algorithm, expiresIn: "5m", }); - await expect(validateAndDecodeJWT(token)).rejects.toThrow(/signature/i); + await expect(JWT.validateAndDecode(token)).rejects.toThrow(/signature/i); }); test(`throws "TokenExpiredError" when called with a token with an expired maxAge`, async () => { const token = jwt.sign(MOCK_JWT_PAYLOAD, ENV.JWT.PRIVATE_KEY, { audience: ENV.CONFIG.API_BASE_URL, issuer: ENV.JWT.ISSUER, - algorithm: ENV.JWT.ALGORITHM, + algorithm, expiresIn: "0s", }); - await expect(validateAndDecodeJWT(token)).rejects.toThrow(/expired/i); + await expect(JWT.validateAndDecode(token)).rejects.toThrow(/expired/i); }); }); }); diff --git a/src/utils/jwt.ts b/src/utils/jwt.ts index 227ae671..26a0867a 100644 --- a/src/utils/jwt.ts +++ b/src/utils/jwt.ts @@ -1,60 +1,109 @@ -import jwt from "jsonwebtoken"; +import jwt, { type Algorithm } from "jsonwebtoken"; import { ENV } from "@/server/env"; +import { AuthError } from "@/utils/httpErrors.js"; -/** - * Validates and decodes a JSON Web Token (JWT) using the provided private key - * and algorithm. It checks if the token is valid based on the shared parameters - * and returns the decoded payload if successful. - * - * @param token - The JWT to be validated and decoded. - * @returns A Promise that resolves to a 'FixitApiJwtPayload' object representing the decoded JWT payload. - * @throws An error if the token is invalid. - */ -export const validateAndDecodeJWT = async < - DecodedPayload extends Record = Record, ->( - token: string -): Promise => { - return new Promise((resolve, reject) => { - jwt.verify( - token, - ENV.JWT.PRIVATE_KEY, - { - audience: ENV.CONFIG.API_BASE_URL, - issuer: ENV.JWT.ISSUER, - algorithms: [ENV.JWT.ALGORITHM], - maxAge: ENV.JWT.EXPIRES_IN, - }, - (err, decoded) => { - if (err || !decoded) { - reject( - new Error( - err?.name === "TokenExpiredError" - ? "Token expired" - : err?.name === "JsonWebTokenError" - ? "Signature verification failed" - : "Invalid token" - ) - ); - } - resolve(decoded as DecodedPayload); - } - ); - }); +// Ensure the provided JWT algorithm is supported +if (!/^[HRE]S(256|384|512)$/.test(ENV.JWT.ALGORITHM)) { + throw new Error("Unsupported JWT algorithm"); +} + +const jwtAlgorithm = ENV.JWT.ALGORITHM as Algorithm; + +type BaseJwtPayload = jwt.JwtPayload & { + id: string; + [key: string]: unknown; }; /** - * Signs and encodes a JSON Web Token (JWT) using the provided payload and env vars. - * - * @param payload - The payload data for the JWT; if `payload.id` is provided, it will be used as the JWT subject. - * @returns The signed and encoded JWT string. + * JSON Web Token base util class for creating and validating JWTs. */ -export const signAndEncodeJWT = (payload: jwt.JwtPayload & { id?: string }) => { - return jwt.sign(payload, ENV.JWT.PRIVATE_KEY, { - audience: ENV.CONFIG.API_BASE_URL, - issuer: ENV.JWT.ISSUER, - algorithm: ENV.JWT.ALGORITHM, - expiresIn: ENV.JWT.EXPIRES_IN, - subject: payload.id, - }); -}; +export class JWT { + /** + * Signs and encodes a JSON Web Token (JWT) using the provided payload and env vars. + * @param payload - The payload data for the JWT; if `payload.id` is provided, it will be used as the JWT subject. + * @returns The signed and encoded JWT string. + */ + static readonly signAndEncode = (payload: Payload) => { + return jwt.sign(payload, ENV.JWT.PRIVATE_KEY, { + audience: ENV.CONFIG.API_BASE_URL, + issuer: ENV.JWT.ISSUER, + algorithm: jwtAlgorithm, + expiresIn: ENV.JWT.EXPIRES_IN, + subject: payload.id, + }); + }; + + static readonly DEFAULT_DECODE_ERR_MSGS: { + [key: string]: string; + TokenExpiredError: string; + JsonWebTokenError: string; + default: string; + } = { + TokenExpiredError: "Your token has expired", + JsonWebTokenError: "Signature verification failed", + default: "Invalid token", + }; + + /** + * Validates and decodes a JSON Web Token (JWT) using the provided private key + * and algorithm. It checks if the token is valid based on the shared parameters + * and returns the decoded payload if successful. + * @param token - The JWT to be validated and decoded. + * @returns A Promise that resolves to a 'FixitApiJwtPayload' object representing the decoded JWT payload. + * @throws An error if the token is invalid. + */ + static readonly validateAndDecode = async ( + token: string, + { + decodeErrMsgs = JWT.DEFAULT_DECODE_ERR_MSGS, + shouldStripInternalFields = true, + }: { + decodeErrMsgs?: Partial; + shouldStripInternalFields?: boolean; + } = {} + ): Promise => { + let decodedPayload = await new Promise((resolve, reject) => { + jwt.verify( + token, + ENV.JWT.PRIVATE_KEY, + { + audience: ENV.CONFIG.API_BASE_URL, + issuer: ENV.JWT.ISSUER, + algorithms: [jwtAlgorithm], + maxAge: ENV.JWT.EXPIRES_IN, + }, + (err, decoded) => { + if (err || !decoded) { + const errName = err?.name ?? "default"; + const errMsg = + decodeErrMsgs[errName] ?? + decodeErrMsgs.default ?? + JWT.DEFAULT_DECODE_ERR_MSGS.default; + reject(new AuthError(errMsg)); + } + resolve(decoded as Payload); + } + ); + }); + + if (shouldStripInternalFields) { + decodedPayload = this.stripInternalPayloadFields(decodedPayload) as Payload; + } + + return decodedPayload; + }; + + /** + * Strips internal JWT payload fields from a given payload. + * @param payload - The JWT payload to strip internal fields from. + * @returns The stripped payload. + */ + static readonly stripInternalPayloadFields = ( + payload: Payload + ) => { + // Filter out the internal JWT fields via destructuring. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { iss, sub, aud, exp, nbf, iat, jti, ...strippedPayload } = payload; + return strippedPayload; + }; +} diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 1c6fc6a1..0e2788d4 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -8,10 +8,11 @@ import * as Sentry from "@sentry/node"; import chalk, { type ChalkInstance } from "chalk"; import dayjs from "dayjs"; import { ENV } from "@/server/env"; -import type { HttpErrorInterface } from "./httpErrors"; /* eslint-disable no-console */ +const jsonStrSpaces = ENV.IS_PROD ? 0 : 2; + /** * Returns a log message string. * - Log message format: `"[