diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e45219e..4c414ae 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,6 @@ #################################################### # -# List of approvers for project +# List of approvers for the Directory JavaScript SDK project # ##################################################### # @@ -12,8 +12,8 @@ # the repository. Unless a later match takes precedence, # the following users/teams will be requested for # review when someone opens a pull request. -* @agntcy/ +* @agntcy/dir-maintainers # Enforces admin protections for repo configuration via probot settings app. # ref: https://github.com/probot/settings#security-implications -.github/settings.yml @agntcy/ +.github/settings.yml @agntcy/dir-maintainers diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a06ee2f..4d32c11 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -30,10 +30,7 @@ body: If applicable, provide the version number or release tag where this issue was encountered options: - - v1.0.0 - - v1.0.1 - - v1.0.2 - - v1.0.3 + - v1.1.0 default: 0 validations: required: false @@ -55,7 +52,7 @@ body: label: Checklist description: By submitting this issue, you agree to the following options: - - label: I have read the [contributing guidelines](/agntcy/repo-template/blob/main/CONTRIBUTING.md) + - label: I have read the [contributing guidelines](/agntcy/dir-sdk-javascript/blob/main/CONTRIBUTING.md) required: true - label: I have verified this does not duplicate an existing issue required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index e1c9470..37d6f68 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -48,7 +48,7 @@ body: label: Checklist description: By submitting this request, you agree to the following options: - - label: I have read the [contributing guidelines](/agntcy/repo-template/blob/main/CONTRIBUTING.md) + - label: I have read the [contributing guidelines](/agntcy/dir-sdk-javascript/blob/main/CONTRIBUTING.md) required: true - label: | I have verified this does not duplicate an existing feature request diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3392ce7..9e94fc0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -19,7 +19,7 @@ much longer to review, or result in it not being reviewed at all. ## Checklist -- [ ] I have read the [contributing guidelines](/agntcy/repo-template/blob/main/CONTRIBUTING.md) +- [ ] I have read the [contributing guidelines](/agntcy/dir-sdk-javascript/blob/main/CONTRIBUTING.md) - [ ] Existing issues have been referenced (where applicable) - [ ] I have verified this change is not present in other open pull requests - [ ] Functionality is documented diff --git a/.github/settings.yml b/.github/settings.yml index 69fdc66..05e63b7 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -5,13 +5,13 @@ repository: # See https://developer.github.com/v3/repos/#edit for all available settings. # The name of the repository. Changing this will rename the repository - name: repo-template + name: dir-sdk-javascript # A short description of the repository that will show up on GitHub - description: Agntcy Project Repo Template + description: Directory JavaScript SDK # A URL with more information about the repository - homepage: https://www.example.com + homepage: https://docs.agntcy.org/dir/directory-sdk/#javascript-sdk # A comma-separated list of topics to set on the repository topics: github, probot @@ -60,60 +60,56 @@ repository: # TODO: Enable this section once GitHub teams have been created & granted access to this repository # See https://developer.github.com/v3/teams/#add-or-update-team-repository for options -# teams: -# # The permission to grant the team. Can be one of: -# # * `pull` - can pull, but not push to or administer this repository. -# # * `push` - can pull and push, but not administer this repository. -# # * `admin` - can pull, push and administer this repository. -# # * `maintain` - Recommended for project managers who need to manage the -# # repository without access to sensitive or destructive actions. -# # * `triage` - Recommended for contributors who need to proactively manage -# # issues and pull requests without write access. -# - name: -# permission: admin -# - name: -# permission: maintain -# - name: -# permission: triage + teams: + # The permission to grant the team. Can be one of: + # * `pull` - can pull, but not push to or administer this repository. + # * `push` - can pull and push, but not administer this repository. + # * `admin` - can pull, push and administer this repository. + # * `maintain` - Recommended for project managers who need to manage the + # repository without access to sensitive or destructive actions. + # * `triage` - Recommended for contributors who need to proactively manage + # issues and pull requests without write access. + - name: dir-maintainers + permission: admin # TODO: Enable this section once repository has been created & no further modifications are needed -# branches: -# - name: main -# # https://developer.github.com/v3/repos/branches/#update-branch-protection -# # Branch Protection settings. Set to null to disable -# protection: -# # Required. Require at least one approving review on a pull request, -# # before merging. Set to null to disable. -# required_pull_request_reviews: -# # The number of approvals required. (1-6) -# required_approving_review_count: 1 -# # Dismiss approved reviews automatically when a new commit is pushed. -# dismiss_stale_reviews: true -# # Blocks merge until code owners have reviewed. -# require_code_owner_reviews: true -# # Specify which users and teams can dismiss pull request reviews. Pass -# # an empty dismissal_restrictions object to disable. User and team -# # dismissal_restrictions are only available for organization-owned -# # repositories. Omit this parameter for personal repositories. -# dismissal_restrictions: -# users: [] -# teams: [] -# # Required. Require status checks to pass before merging. Set to null to disable -# required_status_checks: -# # Required. Require branches to be up to date before merging. -# strict: true -# # Required. The list of required status checks in order to merge into this branch -# contexts: [] -# # Required. Enforce all configured restrictions for administrators. Set to -# # true to enforce required status checks for repository administrators. -# # Set to null to disable. -# enforce_admins: true -# # Prevent merge commits from being pushed to matching branches -# required_linear_history: true -# # Required. Restrict who can push to this branch. Team and user -# # restrictions are only available for organization-owned repositories. Set -# # to null to disable. -# restrictions: -# apps: [] -# users: [] -# teams: [] +branches: + - name: main + # https://developer.github.com/v3/repos/branches/#update-branch-protection + # Branch Protection settings. Set to null to disable + protection: + # Required. Require at least one approving review on a pull request, + # before merging. Set to null to disable. + required_pull_request_reviews: + # The number of approvals required. (1-6) + required_approving_review_count: 1 + # Dismiss approved reviews automatically when a new commit is pushed. + dismiss_stale_reviews: true + # Blocks merge until code owners have reviewed. + require_code_owner_reviews: true + # Specify which users and teams can dismiss pull request reviews. Pass + # an empty dismissal_restrictions object to disable. User and team + # dismissal_restrictions are only available for organization-owned + # repositories. Omit this parameter for personal repositories. + dismissal_restrictions: + users: [] + teams: [] + # Required. Require status checks to pass before merging. Set to null to disable + required_status_checks: + # Required. Require branches to be up to date before merging. + strict: true + # Required. The list of required status checks in order to merge into this branch + contexts: [] + # Required. Enforce all configured restrictions for administrators. Set to + # true to enforce required status checks for repository administrators. + # Set to null to disable. + enforce_admins: true + # Prevent merge commits from being pushed to matching branches + required_linear_history: true + # Required. Restrict who can push to this branch. Team and user + # restrictions are only available for organization-owned repositories. Set + # to null to disable. + restrictions: + apps: [] + users: [] + teams: [] diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..a6c64eb --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,87 @@ +# Copyright AGNTCY Contributors (https://github.com/agntcy) +# SPDX-License-Identifier: Apache-2.0 + +name: CI + +on: + push: + branches: + - main + tags: + - "v*.*.*" + pull_request: + +permissions: + contents: write + packages: write + id-token: write + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + outputs: + code: ${{ steps.result.outputs.code }} + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Check for code changes + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + id: filter + with: + filters: | + code: + - '!docs/**' + - '!README.md' + - '!CHANGELOG.md' + - '!CONTRIBUTING.md' + - '!CODE_OF_CONDUCT.md' + - '!SECURITY.md' + - '!LICENSE.md' + - '!MAINTAINERS.md' + - '!CONTRIBUTORS.md' + - '!AUTHZ.md' + - '!API_SPEC.md' + - '!RELEASE.md' + - '!.gitignore' + + - name: Determine result + id: result + run: | + # Always run for tag pushes (releases), otherwise use paths-filter result + if [[ "${{ startsWith(github.ref, 'refs/tags/') }}" == "true" ]]; then + echo "code=true" >> $GITHUB_OUTPUT + else + echo "code=${{ steps.filter.outputs.code }}" >> $GITHUB_OUTPUT + fi + + test: + name: Test + needs: + - changes + uses: ./.github/workflows/reusable-test-sdk.yaml + with: + image_repo: ghcr.io/agntcy + image_tag: ${{ github.sha }} + + success: + name: Success + if: ${{ !cancelled() && !contains(needs.*.result, 'cancelled') && !contains(needs.*.result, 'failure') }} + needs: + - changes + - test + runs-on: ubuntu-latest + steps: + - name: Echo Success + run: | + if [[ "${{ needs.changes.outputs.code }}" == "true" ]]; then + echo "::notice Success! All code checks passed." + else + echo "::notice Success! No code changes detected, skipped builds and tests." + fi diff --git a/.github/workflows/dependency.yml b/.github/workflows/dependency.yml index d144b8e..536be13 100644 --- a/.github/workflows/dependency.yml +++ b/.github/workflows/dependency.yml @@ -1,10 +1,11 @@ --- name: dependency -on: - # Review dependencies on pull requests to the default branch - pull_request: - branches: ["main"] +# TODO: enable dependency review after migration +# on: +# Review dependencies on pull requests to the default branch +# pull_request: +# branches: ["main"] # Declare default permissions as read-only permissions: read-all diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3c005ca..da85f2a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,13 +1,14 @@ --- name: lint -on: - push: - # Run superlinter on pushes to default branch - branches: - - main - # Run superlinter on pull request events - pull_request: +# TODO: enable linter after migration +# on: +# push: +# # Run superlinter on pushes to default branch +# branches: +# - main +# # Run superlinter on pull request events +# pull_request: # Declare default permissions as read-only permissions: read-all diff --git a/.github/workflows/reusable-test-sdk.yaml b/.github/workflows/reusable-test-sdk.yaml new file mode 100644 index 0000000..f745fda --- /dev/null +++ b/.github/workflows/reusable-test-sdk.yaml @@ -0,0 +1,74 @@ +# Copyright AGNTCY Contributors (https://github.com/agntcy) +# SPDX-License-Identifier: Apache-2.0 + +name: Test SDK + +on: + workflow_call: + inputs: + image_repo: + required: true + type: string + description: "Image repo to use." + image_tag: + required: true + type: string + description: "Image tag to use." + +permissions: + id-token: write + contents: read + +jobs: + javascript: + name: JavaScript + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Login to ghcr.io + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ghcr.io + username: notused + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Install Task + uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 #v2.0.0 + + - name: Setup Node env + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: "24.x" + registry-url: https://registry.npmjs.org/ + scope: "@agntcy" + + - name: Download artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: tmp/artifacts + merge-multiple: true + + - name: Cache npm dependencies + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + with: + path: "~/.npm" + key: node-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + node-${{ runner.os }}- + + - name: Add dependencies for SDKs testing + env: + CLIENT_ID: "https://github.com/${{ github.repository }}/.github/workflows/reusable-test-sdk.yaml@${{ github.ref }}" + run: | + task sdk:deps:cicd:iodc-token-generation >> $GITHUB_ENV + + - name: Test JavaScript SDK + env: + CHART_VERSION: "0.0.0-latest" + IMAGE_TAG: latest + run: | + task sdk:test:javascript diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a325117 --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +# IDEs +.vscode/ +.idea/ +.continue/ + +# Secrets +.env +.envrc + +# Builds +.bin/ + +# Python +dist/ +__pycache__/ +*.egg-info +*.pyc +*.pyo +*.pyd +.venv/ + +# JS +node_modules + +# Helm +install/charts/dir/charts/ +install/charts/dir/apiserver/charts/ +install/charts/dirctl/charts/ +install/charts/envoy-authz/charts/ + +# MacOS +.DS_Store + +# Test output +e2e/tmp/* + +# Coverage output +.coverage/ + +# Licensei +.licensei.cache + +tmp/ + +# AI +.cursor/ + +# Go workspace files +go.work +go.work.sum + +# MCP server binary +mcp/mcp-server + +# Local Build output +build/ +mcp/debug/ + +# Flutter GUI +gui/.dart_tool/ +gui/.flutter-plugins +gui/.flutter-plugins-dependencies +gui/build/ +gui/android/ +gui/ios/ +gui/linux/ +gui/web/ +gui/windows/ +gui/macos/ + +gui/.env diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..9378dac --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +@agntcy:registry=https://registry.npmjs.org/ +@buf:registry=https://buf.build/gen/npm/v1/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..24ac038 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,16 @@ +{ + "printWidth" : 80, + "tabWidth" : 2, + "useTabs" : false, + "semi" : true, + "singleQuote" : true, + "quoteProps" : "preserve", + "bracketSpacing" : false, + "trailingComma" : "all", + "arrowParens" : "always", + "embeddedLanguageFormatting" : "off", + "bracketSameLine" : true, + "singleAttributePerLine" : false, + "htmlWhitespaceSensitivity" : "strict", + "plugins": ["prettier-plugin-organize-imports"], +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 46ad21b..f208450 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ # Changelog -## 0.0.1 (2025-02-04) +[agntcy/dir]: https://github.com/agntcy/dir +[agntcy/dir-sdk-javascript]: https://github.com/agntcy/dir-sdk-javascript -### Feat +## 1.1.0 (2026-04-10) -- Add commitizen -- Initial Commit to create the scaffolding to template repo and relevant github actions +### Added + +The Directory JavaScript SDK has been migrated from the [agntcy/dir][agntcy/dir] +repository to the [agntcy/dir-sdk-javascript][agntcy/dir-sdk-javascript] repository. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 794ed8c..8a4d29e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # How to Contribute -Thanks for your interest in contributing to ``! Here are a few +Thanks for your interest in contributing to the Directory JavaScript SDK! Here are a few general guidelines on contributing and reporting bugs that we ask you to review. Following these guidelines helps to communicate that you respect the time of the contributors managing and developing this open source project. In return, they @@ -18,7 +18,7 @@ in any real-time space e.g., Slack, Discord, etc. Before reporting a new issue, please ensure that the issue was not already reported or fixed by searching through our [issues -list](https://github.com/org_name/repo_name/issues). +list](https://github.com/agntcy/dir-sdk-javascript/issues). When creating a new issue, please be sure to include a **title and clear description**, as much relevant information as possible, and, if possible, a @@ -39,7 +39,7 @@ major version release. ## Other Ways to Contribute -We welcome anyone that wants to contribute to `` to triage and +We welcome anyone that wants to contribute to the Directory JavaScript SDK to triage and reply to open issues to help troubleshoot and fix existing bugs. Here is what you can do: @@ -47,12 +47,12 @@ you can do: _[Reporting Issues](#reporting-issues)_ section, providing feedback to the issue's author on what might be missing. - Review and update the existing content of our - [Wiki](https://github.com/org_name/repo_name/wiki) with up-to-date + [Wiki](https://github.com/agntcy/dir-sdk-javascript/wiki) with up-to-date instructions and code samples. - Review existing pull requests, and testing patches against real existing - applications that use ``. + applications that use the Directory JavaScript SDK. - Write a test, or add a missing test case to an existing test. -Thanks again for your interest on contributing to ``! +Thanks again for your interest on contributing to the Directory JavaScript SDK! :heart: diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 422fdc0..6560f80 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -1,5 +1,8 @@ # Maintainers -- [GitHub Username](https://github.com/username) -- [GitHub Username](https://github.com/username) -- [GitHub Username](https://github.com/username) +- [ramizpolic](https://github.com/ramizpolic), Ramiz Polic +- [muscariello](https://github.com/muscariello), Luca Muscariello +- [paralta](https://github.com/paralta), Catarina Paralta +- [adamtagscherer](https://github.com/adamtagscherer), Adam Tagscherer +- [tkircsi](https://github.com/tkircsi), Tibor Kircsi +- [csirmazbendeguz](https://github.com/csirmazbendeguz), Bendegúz Csirmaz diff --git a/README.md b/README.md index fe2f9fe..9910663 100644 --- a/README.md +++ b/README.md @@ -1,121 +1,194 @@ -# Agntcy Repo Project Template - -[![Release](https://img.shields.io/github/v/release/agntcy/repo-template?display_name=tag)](CHANGELOG.md) -[![Lint](https://github.com/agntcy/repo-template/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/marketplace/actions/super-linter) -[![Contributor-Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-fbab2c.svg)](CODE_OF_CONDUCT.md) - -## Before You Start - -As much as possible, we have tried to provide enough tooling to get you up and -running quickly and with a minimum of effort. This includes sane defaults for -documentation; templates for bug reports, feature requests, and pull requests; -and [GitHub Actions](https://github.com/features/actions) that will -automatically manage stale issues and pull requests. This latter defaults to -labeling issues and pull requests as stale after 60 days of inactivity, and -closing them after 7 additional days of inactivity. These -[defaults](.github/workflows/stale.yml) and more can be configured. For -configuration options, please consult the documentation for the [stale -action](https://github.com/actions/stale). - -In trying to keep this template as generic and reusable as possible, there are -some things that were omitted out of necessity and others that need a little -tweaking. Before you begin developing in earnest, there are a few changes that -need to be made: - -- [ ] ✅ Select an [OSI-approved license](https://opensource.org/licenses) for - your project. This can easily be achieved through the 'Add File' button on the - GitHub UI, naming the file `LICENSE`, and selecting your desired license from - the provided list. -- [ ] Update the `` placeholder in this file to reflect the name - of the license you selected above. -- [ ] Replace `` in - [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) with a suitable communication - channel. -- [ ] Change references to `org_name` to the name of the org your repository belongs - to (eg. `agntcy`): - - [ ] In [`README.md`](README.md) - - [ ] In [`CONTRIBUTING.md`](CONTRIBUTING.md) -- [ ] Change references to `repo_name` to the name of your new repository: - - [ ] In [`README.md`](README.md) - - [ ] In [`CONTRIBUTING.md`](CONTRIBUTING.md) -- [ ] Update the Release and Lint `README` badges to point to your project URL. -- [ ] Update the links to `CONTRIBUTING.md` to point to your project URL: - - [ ] In - [`.github/ISSUE_TEMPLATE/bug_report.yml`](.github/ISSUE_TEMPLATE/bug_report.yml) - - [ ] In - [`.github/ISSUE_TEMPLATE/feature_request.yml`](.github/ISSUE_TEMPLATE/feature_request.yml) - - [ ] In - [`.github/pull_request_template.md`](.github/pull_request_template.md) -- [ ] Update the `Affected Version` tags in - [`.github/ISSUE_TEMPLATE/bug_report.yml`](.github/ISSUE_TEMPLATE/bug_report.yml) - if applicable. -- [ ] Replace the `` placeholder with the name of your project: - - [ ] In [`CONTRIBUTING.md`](CONTRIBUTING.md) - - [ ] In [`SECURITY.md`](SECURITY.md) -- [ ] Add names and contact information for the project maintainers to - [`MAINTAINERS.md`](MAINTAINERS.md). -- [ ] Update the `` placeholder in - [`.github/CODEOWNERS`](.github/CODEOWNERS) as well as the - `` and `` entries. -- [ ] Delete the release placeholder content in [`CHANGELOG.md`](CHANGELOG.md). - We encourage you to [keep a changelog](https://keepachangelog.com/en/1.0.0/). -- [ ] Configure [`.github/dependabot.yml`](.github/dependabot.yml) for your project's - language and tooling dependencies. -- [ ] In [`.github/settings.yml`](.github/settings.yml), update the following fields: - - [ ] `name`: Replace with the repository name for your project - - [ ] `description`: A short, 1-2 sentence description of your project - - [ ] `teams`: Uncomment and update the GitHub team names and permissions as appropriate - - [ ] `branches`: Uncomment and enable branch protection settings for your - project _(please **do not** disable branch protection entirely!)_ -- [ ] Replace the generic content in this file with the relevant details about - your project. -- [ ] 🚨 Delete this section of the `README`! +# Directory JavaScript SDK + +[![Release](https://img.shields.io/github/v/release/agntcy/dir-sdk-javascript)](CHANGELOG.md) ## About The Project -Provide some information about what the project is/does. +Dir JavaScript SDK provides a simple way to interact with the Directory API. +It allows developers to integrate and use Directory functionality from their applications with ease. +The SDK supports both JavaScript and TypeScript applications. + +**Note for users:** The SDK is intended for use in Node.js applications and will not work in Web applications. + +The Directory SDK provides comprehensive access to all Directory APIs with a simple, intuitive interface: + +### **Store API** +- **Record Management**: Push records to the store and pull them by reference +- **Metadata Operations**: Look up record metadata without downloading full content +- **Data Lifecycle**: Delete records permanently from the store +- **Referrer Support**: Push and pull artifacts for existing records +- **Sync Management**: Manage storage synchronization policies between Directory servers + +### **Search API** +- **Flexible Search**: Search stored records using text, semantic, and structured queries +- **Advanced Filtering**: Filter results by metadata, content type, and other criteria + +### **Routing API** +- **Network Publishing**: Publish records to make them discoverable across the network +- **Content Discovery**: List and query published records across the network +- **Network Management**: Unpublish records to remove them from network discovery + +### **Signing and Verification** +- **Local Signing**: Sign records locally using private keys or OIDC-based authentication. + Requires [dirctl](https://github.com/agntcy/dir/releases) binary to perform signing. +- **Remote Verification**: Verify record signatures using the Directory gRPC API + +### **Developer Experience** +- **Type Safety**: Full type hints for better IDE support and fewer runtime errors +- **Async Support**: Non-blocking operations with streaming responses for large datasets +- **Error Handling**: Comprehensive gRPC error handling with detailed error messages +- **Configuration**: Flexible configuration via environment variables or direct instantiation ## Getting Started -To get a local copy up and running follow these simple steps. +Install the SDK using one of available JS package managers like [npm](https://www.npmjs.com/) ### Prerequisites -This is an example of how to list things you need to use the software and how to -install them. +- [NodeJS](https://nodejs.org/en/) - JavaScript runtime +- [npm](https://www.npmjs.com/) - Package manager +- [dirctl](https://github.com/agntcy/dir/releases) - Directory CLI binary +- Directory server instance (see setup below) -- npm +### 1. Server Setup - ```sh - npm install npm@latest -g - ``` +**Option A: Local Development Server** -### Installation +```bash +# Clone the repository and start the server using Taskfile +task server:start +``` + +**Option B: Custom Server** + +```bash +# Set your Directory server address +export DIRECTORY_CLIENT_SERVER_ADDRESS="your-server:8888" +``` + +### 2. SDK Installation -1. Clone the repository +```bash +# Add the Directory SDK +npm install agntcy-dir +``` - ```sh - git clone https://github.com/org_name/repo_name.git - ``` +### Installation -2. Install npm packages +1. Initialize the project: +```bash +npm init -y +``` - ```sh - npm install - ``` +2. Add the SDK to your project: +```bash +npm install agntcy-dir +``` ## Usage -Use this space to show useful examples of how a project can be used. Additional -screenshots, code examples and demos work well in this space. You may also link -to more resources. +The SDK can be configured via environment variables or direct instantiation: + +```js +// Environment variables (insecure mode) +process.env.DIRECTORY_CLIENT_SERVER_ADDRESS = "localhost:8888"; +process.env.DIRCTL_PATH = "/path/to/dirctl"; + +// Environment variables (X.509 authentication) +process.env.DIRECTORY_CLIENT_SERVER_ADDRESS = "localhost:8888"; +process.env.DIRECTORY_CLIENT_AUTH_MODE = "x509"; +process.env.DIRECTORY_CLIENT_SPIFFE_SOCKET_PATH = "/tmp/agent.sock"; + +// Environment variables (JWT authentication) +process.env.DIRECTORY_CLIENT_SERVER_ADDRESS = "localhost:8888"; +process.env.DIRECTORY_CLIENT_AUTH_MODE = "jwt"; +process.env.DIRECTORY_CLIENT_SPIFFE_SOCKET_PATH = "/tmp/agent.sock"; +process.env.DIRECTORY_CLIENT_JWT_AUDIENCE = "spiffe://example.org/dir-server"; + +// Or configure directly +import {Config, Client} from 'agntcy-dir'; + +// Insecure mode (default, for development only) +const config = new Config( + serverAddress="localhost:8888", + dirctlPath="/usr/local/bin/dirctl" +); +const client = new Client(config); + +// X.509 authentication with SPIRE +const x509Config = new Config( + "localhost:8888", + "/usr/local/bin/dirctl", + "/tmp/agent.sock", // SPIFFE socket path + "x509" // auth mode +); +const x509Transport = await Client.createGRPCTransport(x509Config); +const x509Client = new Client(x509Config, x509Transport); + +// JWT authentication with SPIRE +const jwtConfig = new Config( + "localhost:8888", + "/usr/local/bin/dirctl", + "/tmp/agent.sock", // SPIFFE socket path + "jwt", // auth mode + "spiffe://example.org/dir-server" // JWT audience +); +const jwtTransport = await Client.createGRPCTransport(jwtConfig); +const jwtClient = new Client(jwtConfig, jwtTransport); +``` + +### OAuth 2.0 for Directory bearer auth + +The SDK supports OIDC/OAuth for Directory bearer authentication on gRPC: + +- Interactive login via Authorization Code + PKCE with a loopback callback (`authenticateOAuthPkce()`) +- Pre-issued access token via `DIRECTORY_CLIENT_AUTH_TOKEN` + +Interactive PKCE sessions are cached alongside other Directory tooling at `$XDG_CONFIG_HOME/dirctl/auth-token.json` or `~/.config/dirctl/auth-token.json`. Pre-issued tokens from configuration are used directly and are not written to the cache by the constructor. + +Use this mode when your deployment expects a **Bearer access token** on gRPC (for example via a gateway that validates OIDC tokens). Register your IdP application with a **redirect URI** that matches `DIRECTORY_CLIENT_OIDC_REDIRECT_URI` exactly (for example `http://localhost:8484/callback`). The SDK starts a short-lived HTTP server on loopback to receive the authorization redirect. + +Some IdPs use **public clients** with PKCE; your IdP may still expect a `client_secret` field in configuration. In that case, use a **random placeholder** from environment variables, not a real secret in source code. + +**Important:** The default in-repo Envoy authorization stack validates **GitHub** tokens. OIDC access tokens from your IdP only work if your environment’s gateway or auth service is configured to accept them. + +```bash +export DIRECTORY_CLIENT_AUTH_MODE="oidc" +export DIRECTORY_CLIENT_SERVER_ADDRESS="directory.example.com:443" +export DIRECTORY_CLIENT_OIDC_ISSUER="https://your-idp-provider.example.com" +export DIRECTORY_CLIENT_OIDC_CLIENT_ID="your-app-client-id" +# Optional placeholder for public clients: +export DIRECTORY_CLIENT_OIDC_CLIENT_SECRET="random-non-secret-string" +export DIRECTORY_CLIENT_OIDC_REDIRECT_URI="http://localhost:8484/callback" +# Optional: comma-separated scopes +# export DIRECTORY_CLIENT_OIDC_SCOPES="openid,profile,email" +``` + +```js +import { Client } from 'agntcy-dir'; + +// After exporting the variables above (or building a Config with authMode: 'oidc'): +const client = new Client(); +await client.authenticateOAuthPkce(); +``` + +For custom transports, call `Client.createGRPCTransport(oidcConfig, { oidcTokenHolder })` with an `OAuthTokenHolder` (exported from this package). The usual path is `new Client(oidcConfig)`, which wires the holder and transport automatically. + +### Usage Examples + +See the [Example JavaScript Project](../examples/example-js/) for a complete working example that demonstrates all SDK features. + +```bash +npm install +npm run example +``` _For more examples, please refer to the [Documentation](https://example.com) or -the [Wiki](https://github.com/org_name/repo_name/wiki)_ +the [Wiki](https://github.com/agntcy/dir-sdk-javascript/wiki)_ ## Roadmap -See the [open issues](https://github.com/org_name/repo_name/issues) for a list +See the [open issues](https://github.com/agntcy/dir-sdk-javascript/issues) for a list of proposed features (and known issues). ## Contributing @@ -127,17 +200,10 @@ appreciated**. For detailed contributing guidelines, please see ## License -Distributed under the `` License. See [LICENSE](LICENSE) for more +Distributed under the Apache 2.0 License. See [LICENSE](LICENSE) for more information. ## Contact -Your Name - [@github_handle](https://github.com/github_handle) - email - Project Link: -[https://github.com/org_name/repo_name](https://github.com/org_name/repo_name) - -## Acknowledgements - -This template was adapted from -[https://github.com/othneildrew/Best-README-Template](https://github.com/othneildrew/Best-README-Template). +[https://github.com/agntcy/dir-sdk-javascript](https://github.com/agntcy/dir-sdk-javascript) diff --git a/SECURITY.md b/SECURITY.md index aca6d58..2b3249f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,7 +1,7 @@ # Security Policies and Procedures This document outlines security procedures and general policies for the -`` project. +Directory JavaScript SDK project. - [Security Policies and Procedures](#security-policies-and-procedures) - [Disclosing a security issue](#disclosing-a-security-issue) @@ -10,12 +10,12 @@ This document outlines security procedures and general policies for the ## Disclosing a security issue -The `` maintainers take all security issues in the project -seriously. Thank you for improving the security of ``. We +The Directory JavaScript SDK maintainers take all security issues in the project +seriously. Thank you for improving the security of the Directory JavaScript SDK. We appreciate your dedication to responsible disclosure and will make every effort to acknowledge your contributions. -`` leverages GitHub's private vulnerability reporting. +The Directory JavaScript SDK leverages GitHub's private vulnerability reporting. To learn more about this feature and how to submit a vulnerability report, review [GitHub's documentation on private reporting](https://docs.github.com/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability). diff --git a/Taskfile.deps.yml b/Taskfile.deps.yml new file mode 100644 index 0000000..9c55c5c --- /dev/null +++ b/Taskfile.deps.yml @@ -0,0 +1,80 @@ +# Copyright AGNTCY Contributors (https://github.com/agntcy) +# SPDX-License-Identifier: Apache-2.0 + +version: "3" + +tasks: + default: + cmd: echo "Run the main Taskfile instead of this one." + + deps:bin-dir: + desc: Create bin directory + run: once + cmd: mkdir -p {{.BIN_DIR}} + status: + - test -d {{.BIN_DIR}} + + deps:helm: + desc: Ensure supported Helm version is installed + internal: true + deps: + - deps:bin-dir + preconditions: + - which curl + - which tar + cmds: + - cmd: echo "Downloading Helm v{{.HELM_VERSION}}..." + - cmd: curl -sSfL 'https://get.helm.sh/helm-v{{.HELM_VERSION}}-{{OS}}-{{ARCH}}.tar.gz' --output - | tar xzvOf - '{{OS}}-{{ARCH}}/helm' > {{.HELM_BIN}} + - cmd: chmod +x {{.HELM_BIN}} + status: + - test -x {{.HELM_BIN}} + + deps:kubectl: + desc: Ensure supported kubectl version is installed + internal: true + deps: + - deps:bin-dir + preconditions: + - which curl + cmds: + - cmd: echo "Downloading Kubectl v{{.KUBECTL_VERSION}}..." + - cmd: curl -L "https://dl.k8s.io/release/v{{.KUBECTL_VERSION}}/bin/{{OS}}/{{ARCH}}/kubectl" -o {{.KUBECTL_BIN}} + - cmd: chmod +x {{.KUBECTL_BIN}} + status: + - test -x {{.KUBECTL_BIN}} + + deps:kind: + desc: Ensure supported kind version is installed + internal: true + deps: + - deps:bin-dir + preconditions: + - which go + cmds: + - cmd: echo "Downloading Kind v{{.KIND_VERSION}}..." + - cmd: GOBIN={{.BIN_DIR}} go install sigs.k8s.io/kind@v{{.KIND_VERSION}} + - cmd: mv {{.BIN_DIR}}/kind {{.KIND_BIN}} + status: + - test -x {{.KIND_BIN}} + + deps:cosign: + desc: Install sigstore cosign + internal: true + deps: + - deps:bin-dir + cmds: + - curl -sfL https://github.com/sigstore/cosign/releases/download/v{{.COSIGN_VERSION}}/cosign-{{OS}}-{{ARCH}} -o {{.BIN_DIR}}/cosign-{{.COSIGN_VERSION}} + - chmod +x {{.BIN_DIR}}/cosign-{{.COSIGN_VERSION}} + status: + - test -x {{.COSIGN_BIN}} + + deps:dir-ctl: + desc: Install dir-ctl + internal: true + run: once + vars: + IMAGE_TAG: "{{ .IMAGE_TAG }}" + preconditions: + - which docker + cmds: + - docker image pull ghcr.io/agntcy/dir-ctl:{{.IMAGE_TAG}} diff --git a/Taskfile.test-env.yml b/Taskfile.test-env.yml new file mode 100644 index 0000000..99d8922 --- /dev/null +++ b/Taskfile.test-env.yml @@ -0,0 +1,163 @@ +# Copyright AGNTCY Contributors (https://github.com/agntcy) +# SPDX-License-Identifier: Apache-2.0 + +version: "3" + +tasks: + default: + cmd: echo "Run the main Taskfile instead of this one." + + test-env:kubernetes:setup-cluster: + desc: Create a kind cluster and load Docker images + deps: + - deps:helm + - deps:kubectl + - deps:kind + vars: + KIND_CREATE_OPTS: '{{ .KIND_CREATE_OPTS | default "" }}' + KIND_CLUSTER_CONFIG_PATH: + sh: mktemp + # Port mapping is defined in the format of "hostPort1:containerPort1,hostPort2:containerPort2". + # Container port should match the NodePort of the service defined in the Helm chart. + KIND_EXTRA_PORT_MAPPING: '{{ .KIND_EXTRA_PORT_MAPPING | default "" }}' + cmds: + # Create cluster config + - | + cat < {{.KIND_CLUSTER_CONFIG_PATH}} + kind: Cluster + apiVersion: kind.x-k8s.io/v1alpha4 + nodes: + - role: control-plane + {{ if .KIND_EXTRA_PORT_MAPPING }} + extraPortMappings: + {{ range .KIND_EXTRA_PORT_MAPPING | splitList "," }} + - hostPort: {{ index (splitList ":" .) 0 }} + containerPort: {{ index (splitList ":" .) 1 }} + listenAddress: "127.0.0.1" + protocol: TCP + {{ end }} + {{ end }} + EOF + + # Create cluster + - | + {{ .KIND_BIN }} create cluster {{ .KIND_CREATE_OPTS }} --name {{ .KIND_CLUSTER_NAME }} --config {{ .KIND_CLUSTER_CONFIG_PATH }} + {{ .KIND_BIN }} export kubeconfig --name {{ .KIND_CLUSTER_NAME }} + {{ .KUBECTL_BIN }} cluster-info + + test-env:kubernetes:local: + aliases: [deploy:local] + desc: Deploy a local Directory server in Kubernetes + deps: + - task: test-env:kubernetes:setup-cluster + vars: + KIND_EXTRA_PORT_MAPPING: "{{.API_HOSTPORT}}:{{.API_NODEPORT}},{{.METRICS_HOSTPORT}}:{{.METRICS_NODEPORT}}" + vars: + # Kind args + KIND_CLUSTER_NAME: '{{ .KIND_CLUSTER_NAME | default "sdk-js-test" }}' + KIND_CREATE_OPTS: '{{ .KIND_CREATE_OPTS | default "" }}' + # Helm args + HELM_NAMESPACE: '{{ .HELM_NAMESPACE | default "dir-server" }}' + # API config + API_HOSTPORT: '{{ .API_HOSTPORT | default "8888" }}' + API_NODEPORT: '{{ .API_NODEPORT | default "30088" }}' + METRICS_HOSTPORT: '{{ .METRICS_HOSTPORT | default "9090" }}' + METRICS_NODEPORT: '{{ .METRICS_NODEPORT | default "30090" }}' + cmds: + # TODO: make logic idempotent so that running functional tests does not change previous contexts + + # Generate credentials and htpasswd file (using defaults) + - task: test-env:kubernetes:gen-htpasswd-creds + + # Cleanup credentials on exit (using defaults) + - defer: + task: test-env:kubernetes:cleanup-htpasswd-creds + + # Deploy chart + - | + # Load credentials + source /tmp/dir-htpasswd-creds.env + + {{ .HELM_BIN }} install dir oci://ghcr.io/agntcy/dir/helm-charts/dir \ + --version="{{ .CHART_VERSION }}" \ + --set apiserver.image.tag="{{ .IMAGE_TAG }}" \ + --set apiserver.image.pullSecrets="" \ + --set apiserver.service.type="NodePort" \ + --set apiserver.service.nodePort="{{.API_NODEPORT}}" \ + --set apiserver.service.metricsNodePort="{{.METRICS_NODEPORT}}" \ + {{ if .DIRECTORY_SERVER_PUBLICATION_SCHEDULER_INTERVAL }}--set apiserver.config.publication.scheduler_interval="{{ .DIRECTORY_SERVER_PUBLICATION_SCHEDULER_INTERVAL }}"{{ end }} \ + {{ if .DIRECTORY_SERVER_NAMING_TTL }}--set apiserver.config.naming.ttl="{{ .DIRECTORY_SERVER_NAMING_TTL }}"{{ end }} \ + --set apiserver.config.routing.refresh_interval="1s" \ + --set apiserver.secrets.ociAuth.username="${HTPASSWD_USERNAME}" \ + --set apiserver.secrets.ociAuth.password="${HTPASSWD_PASSWORD}" \ + --set apiserver.zot.authHeader="${HTPASSWD_AUTH_HEADER}" \ + --set-file apiserver.zot.secretFiles.htpasswd="/tmp/dir-htpasswd" \ + --set apiserver.secrets.syncAuth.username="${HTPASSWD_SYNC_USERNAME}" \ + --set apiserver.secrets.syncAuth.password="${HTPASSWD_SYNC_PASSWORD}" \ + {{ if .DIRECTORY_SERVER_RATELIMIT_ENABLED }}--set apiserver.config.ratelimit.enabled="{{ .DIRECTORY_SERVER_RATELIMIT_ENABLED }}"{{ end }} \ + {{ if .DIRECTORY_SERVER_RATELIMIT_GLOBAL_RPS }}--set apiserver.config.ratelimit.global_rps="{{ .DIRECTORY_SERVER_RATELIMIT_GLOBAL_RPS }}"{{ end }} \ + {{ if .DIRECTORY_SERVER_RATELIMIT_GLOBAL_BURST }}--set apiserver.config.ratelimit.global_burst="{{ .DIRECTORY_SERVER_RATELIMIT_GLOBAL_BURST }}"{{ end }} \ + {{ if .DIRECTORY_SERVER_RATELIMIT_PER_CLIENT_RPS }}--set apiserver.config.ratelimit.per_client_rps="{{ .DIRECTORY_SERVER_RATELIMIT_PER_CLIENT_RPS }}"{{ end }} \ + {{ if .DIRECTORY_SERVER_RATELIMIT_PER_CLIENT_BURST }}--set apiserver.config.ratelimit.per_client_burst="{{ .DIRECTORY_SERVER_RATELIMIT_PER_CLIENT_BURST }}"{{ end }} \ + {{ if .DIRECTORY_SERVER_OASF_API_VALIDATION_SCHEMA_URL }}--set apiserver.config.oasf_api_validation.schema_url="{{ .DIRECTORY_SERVER_OASF_API_VALIDATION_SCHEMA_URL }}"{{ end }} \ + --set apiserver.reconciler.image.tag="{{ .IMAGE_TAG }}" \ + --set apiserver.reconciler.config.regsync.interval="10s" \ + --set apiserver.reconciler.config.indexer.interval="10s" \ + --set apiserver.reconciler.config.name.interval="5s" \ + --set apiserver.reconciler.config.signature.interval="5s" \ + --namespace {{ .HELM_NAMESPACE }} \ + --create-namespace \ + --wait \ + --wait-for-jobs \ + --timeout "15m" + + test-env:kubernetes:local:cleanup: + desc: Cleanup Kubernetes environment for local deployment + deps: + - deps:kind + vars: + KIND_CLUSTER_NAME: '{{ .KIND_CLUSTER_NAME | default "sdk-py-test" }}' + cmds: + - "{{ .KIND_BIN }} delete cluster --name {{ .KIND_CLUSTER_NAME }}" + + test-env:kubernetes:gen-htpasswd-creds: + desc: Generate htpasswd credentials and files for DIR deployment + vars: + HTPASSWD_USERNAME: '{{ .HTPASSWD_USERNAME | default "apiserver" }}' + HTPASSWD_SYNC_USERNAME: '{{ .HTPASSWD_SYNC_USERNAME | default "sync-user" }}' + CREDS_FILE: '{{ .CREDS_FILE | default "/tmp/dir-htpasswd-creds.env" }}' + HTPASSWD_FILE: '{{ .HTPASSWD_FILE | default "/tmp/dir-htpasswd" }}' + cmds: + - | + # Generate fresh passwords + HTPASSWD_PASSWORD="$(openssl rand -hex 16)" + HTPASSWD_SYNC_PASSWORD="$(openssl rand -hex 16)" + + # Generate derived values + HTPASSWD_AUTH_HEADER="$(echo -n "{{ .HTPASSWD_USERNAME }}:${HTPASSWD_PASSWORD}" | base64)" + HTPASSWD_ADMIN="$(htpasswd -nbB "{{ .HTPASSWD_USERNAME }}" "${HTPASSWD_PASSWORD}" | sed 's/^{{ .HTPASSWD_USERNAME }}://')" + HTPASSWD_SYNC="$(htpasswd -nbB "{{ .HTPASSWD_SYNC_USERNAME }}" "${HTPASSWD_SYNC_PASSWORD}" | sed 's/^{{ .HTPASSWD_SYNC_USERNAME }}://')" + + # Write credentials to env file (for helm --set commands) + cat > {{ .CREDS_FILE }} << EOF + export HTPASSWD_USERNAME="{{ .HTPASSWD_USERNAME }}" + export HTPASSWD_PASSWORD="${HTPASSWD_PASSWORD}" + export HTPASSWD_AUTH_HEADER="${HTPASSWD_AUTH_HEADER}" + export HTPASSWD_SYNC_USERNAME="{{ .HTPASSWD_SYNC_USERNAME }}" + export HTPASSWD_SYNC_PASSWORD="${HTPASSWD_SYNC_PASSWORD}" + EOF + + # Write htpasswd file (for helm --set-file commands) + cat > {{ .HTPASSWD_FILE }} << EOF + {{ .HTPASSWD_USERNAME }}:${HTPASSWD_ADMIN} + {{ .HTPASSWD_SYNC_USERNAME }}:${HTPASSWD_SYNC} + EOF + + test-env:kubernetes:cleanup-htpasswd-creds: + desc: Cleanup htpasswd credentials and files + vars: + CREDS_FILE: '{{ .CREDS_FILE | default "/tmp/dir-htpasswd-creds.env" }}' + HTPASSWD_FILE: '{{ .HTPASSWD_FILE | default "/tmp/dir-htpasswd" }}' + cmds: + - rm -f {{ .CREDS_FILE }} + - rm -f {{ .HTPASSWD_FILE }} diff --git a/Taskfile.vars.yml b/Taskfile.vars.yml new file mode 100644 index 0000000..b94bebb --- /dev/null +++ b/Taskfile.vars.yml @@ -0,0 +1,18 @@ +# Copyright AGNTCY Contributors (https://github.com/agntcy) +# SPDX-License-Identifier: Apache-2.0 + +version: "3" + +vars: + CHART_VERSION: '{{ .CHART_VERSION | default "0.0.0-latest" }}' + IMAGE_TAG: '{{ .IMAGE_TAG | default "latest" }}' + BIN_DIR: "{{ .ROOT_DIR }}/.bin" + HELM_VERSION: "4.1.3" + HELM_BIN: "{{ .BIN_DIR }}/helm-{{.HELM_VERSION}}" + KUBECTL_VERSION: "1.35.3" + KUBECTL_BIN: "{{ .BIN_DIR }}/kubectl-{{.KUBECTL_VERSION}}" + KIND_VERSION: "0.31.0" + KIND_BIN: "{{ .BIN_DIR }}/kind-{{.KIND_VERSION}}" + COSIGN_VERSION: "3.0.5" + COSIGN_BIN: "{{ .BIN_DIR }}/cosign-{{.COSIGN_VERSION}}" + KIND_CLUSTER_NAME: "sdk-js-test" diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..2a51542 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,93 @@ +# Copyright AGNTCY Contributors (https://github.com/agntcy) +# SPDX-License-Identifier: Apache-2.0 + +version: "3" + +includes: + vars: + taskfile: Taskfile.vars.yml + flatten: true + excludes: [default] + + deps: + taskfile: Taskfile.deps.yml + dir: "{{ .BIN_DIR }}" + flatten: true + excludes: [default] + + test-env: + taskfile: Taskfile.test-env.yml + flatten: true + excludes: [default] + +tasks: + sdk:deps:cicd:iodc-token-generation: + desc: Get Fulcio OIDC token for CICD + requires: + vars: [ CLIENT_ID ] + cmds: + - | + OIDC_TOKEN=$(curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ + "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sigstore" | jq -r '.value') + + CLIENT_ID="{{.CLIENT_ID}}" + PROVIDER_URL="https://token.actions.githubusercontent.com" + + echo "OIDC_PROVIDER_URL=${PROVIDER_URL}" + echo "CLIENT_ID=${CLIENT_ID}" + echo "OIDC_TOKEN=${OIDC_TOKEN}" + + sdk:build:javascript: + desc: Build JavaScript client SDK package + deps: + - task: sdk:deps:javascript + cmds: + - npm run build + preconditions: + - which npm + + sdk:deps:javascript: + desc: Install deps for JavaScript SDK package + deps: + - deps:dir-ctl + - deps:cosign + cmds: + - npm install + preconditions: + - which npm + + sdk:test-env:create: + desc: Create Kubernetes cluster test environment + cmds: + - task: test-env:kubernetes:local + vars: + DIRECTORY_SERVER_PUBLICATION_SCHEDULER_INTERVAL: 1s + DIRECTORY_SERVER_OASF_API_VALIDATION_SCHEMA_URL: "https://schema.oasf.outshift.com" + + sdk:test-env:delete: + desc: Delete Kubernetes cluster test environment + cmds: + - task: test-env:kubernetes:local:cleanup + + sdk:test:javascript: + desc: Test javascript client SDK package + deps: + - task: sdk:deps:javascript + env: + KIND_CLUSTER_NAME: "sdk-js-test" + preconditions: + - which npm + cmds: + - task: sdk:test-env:create + - defer: { task: sdk:test-env:delete } + - | + export COSIGN_PATH="$(printf "%s" "${COSIGN_PATH:-{{ .COSIGN_BIN }}}")" + export DIRCTL_IMAGE_TAG="{{ .IMAGE_TAG }}" + + # Run SDK tests + npm run test + + # Run SDK example to verify it works + #cd {{ .ROOT_DIR }}/sdk/examples/example-js + #npm install + #npm run example diff --git a/api-extractor.json b/api-extractor.json new file mode 100644 index 0000000..713215c --- /dev/null +++ b/api-extractor.json @@ -0,0 +1,422 @@ +/** + * Config file for API Extractor. For more info, please visit: https://api-extractor.com + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + /** + * Optionally specifies another JSON config file that this file extends from. This provides a way for + * standard settings to be shared across multiple projects. + * + * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains + * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be + * resolved using NodeJS require(). + * + * SUPPORTED TOKENS: none + * DEFAULT VALUE: "" + */ + // "extends": "./shared/api-extractor-base.json" + // "extends": "my-package/include/api-extractor-base.json" + /** + * Determines the "" token that can be used with other config file settings. The project folder + * typically contains the tsconfig.json and package.json config files, but the path is user-defined. + * + * The path is resolved relative to the folder of the config file that contains the setting. + * + * The default value for "projectFolder" is the token "", which means the folder is determined by traversing + * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder + * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error + * will be reported. + * + * SUPPORTED TOKENS: + * DEFAULT VALUE: "" + */ + // "projectFolder": "..", + /** + * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor + * analyzes the symbols exported by this module. + * + * The file extension must be ".d.ts" and not ".ts". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + */ + "mainEntryPointFilePath": "/dist/src/index.d.ts", + /** + * A list of NPM package names whose exports should be treated as part of this package. + * + * For example, suppose that Webpack is used to generate a distributed bundle for the project "library1", + * and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part + * of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly + * imports library2. To avoid this, we might specify: + * + * "bundledPackages": [ "library2" ], + * + * This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been + * local files for library1. + * + * The "bundledPackages" elements may specify glob patterns using minimatch syntax. To ensure deterministic + * output, globs are expanded by matching explicitly declared top-level dependencies only. For example, + * the pattern below will NOT match "@my-company/example" unless it appears in a field such as "dependencies" + * or "devDependencies" of the project's package.json file: + * + * "bundledPackages": [ "@my-company/*" ], + */ + "bundledPackages": [], + /** + * Specifies what type of newlines API Extractor should use when writing output files. By default, the output files + * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead. + * To use the OS's default newline kind, specify "os". + * + * DEFAULT VALUE: "crlf" + */ + // "newlineKind": "crlf", + /** + * Specifies how API Extractor sorts members of an enum when generating the .api.json file. By default, the output + * files will be sorted alphabetically, which is "by-name". To keep the ordering in the source code, specify + * "preserve". + * + * DEFAULT VALUE: "by-name" + */ + // "enumMemberOrder": "by-name", + /** + * Set to true when invoking API Extractor's test harness. When `testMode` is true, the `toolVersion` field in the + * .api.json file is assigned an empty string to prevent spurious diffs in output files tracked for tests. + * + * DEFAULT VALUE: "false" + */ + // "testMode": false, + /** + * Determines how the TypeScript compiler engine will be invoked by API Extractor. + */ + "compiler": { + /** + * Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * Note: This setting will be ignored if "overrideTsconfig" is used. + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/tsconfig.json" + */ + // "tsconfigFilePath": "/tsconfig.json", + /** + * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk. + * The object must conform to the TypeScript tsconfig schema: + * + * http://json.schemastore.org/tsconfig + * + * If omitted, then the tsconfig.json file will be read from the "projectFolder". + * + * DEFAULT VALUE: no overrideTsconfig section + */ + // "overrideTsconfig": { + // . . . + // } + /** + * This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended + * and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when + * dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses + * for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck. + * + * DEFAULT VALUE: false + */ + // "skipLibCheck": true, + }, + /** + * Configures how the API report file (*.api.md) will be generated. + */ + "apiReport": { + /** + * (REQUIRED) Whether to generate an API report. + */ + "enabled": false + /** + * The base filename for the API report files, to be combined with "reportFolder" or "reportTempFolder" + * to produce the full file path. The "reportFileName" should not include any path separators such as + * "\" or "/". The "reportFileName" should not include a file extension, since API Extractor will automatically + * append an appropriate file extension such as ".api.md". If the "reportVariants" setting is used, then the + * file extension includes the variant name, for example "my-report.public.api.md" or "my-report.beta.api.md". + * The "complete" variant always uses the simple extension "my-report.api.md". + * + * Previous versions of API Extractor required "reportFileName" to include the ".api.md" extension explicitly; + * for backwards compatibility, that is still accepted but will be discarded before applying the above rules. + * + * SUPPORTED TOKENS: , + * DEFAULT VALUE: "" + */ + // "reportFileName": "", + /** + * To support different approval requirements for different API levels, multiple "variants" of the API report can + * be generated. The "reportVariants" setting specifies a list of variants to be generated. If omitted, + * by default only the "complete" variant will be generated, which includes all @internal, @alpha, @beta, + * and @public items. Other possible variants are "alpha" (@alpha + @beta + @public), "beta" (@beta + @public), + * and "public" (@public only). + * + * DEFAULT VALUE: [ "complete" ] + */ + // "reportVariants": ["public", "beta"], + /** + * Specifies the folder where the API report file is written. The file name portion is determined by + * the "reportFileName" setting. + * + * The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy, + * e.g. for an API review. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/etc/" + */ + // "reportFolder": "/etc/", + /** + * Specifies the folder where the temporary report file is written. The file name portion is determined by + * the "reportFileName" setting. + * + * After the temporary file is written to disk, it is compared with the file in the "reportFolder". + * If they are different, a production build will fail. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/temp/" + */ + // "reportTempFolder": "/temp/", + /** + * Whether "forgotten exports" should be included in the API report file. Forgotten exports are declarations + * flagged with `ae-forgotten-export` warnings. See https://api-extractor.com/pages/messages/ae-forgotten-export/ to + * learn more. + * + * DEFAULT VALUE: "false" + */ + // "includeForgottenExports": false + }, + /** + * Configures how the doc model file (*.api.json) will be generated. + */ + "docModel": { + /** + * (REQUIRED) Whether to generate a doc model file. + */ + "enabled": false + /** + * The output path for the doc model file. The file extension should be ".api.json". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/temp/.api.json" + */ + // "apiJsonFilePath": "/temp/.api.json", + /** + * Whether "forgotten exports" should be included in the doc model file. Forgotten exports are declarations + * flagged with `ae-forgotten-export` warnings. See https://api-extractor.com/pages/messages/ae-forgotten-export/ to + * learn more. + * + * DEFAULT VALUE: "false" + */ + // "includeForgottenExports": false, + /** + * The base URL where the project's source code can be viewed on a website such as GitHub or + * Azure DevOps. This URL path corresponds to the `` path on disk. + * + * This URL is concatenated with the file paths serialized to the doc model to produce URL file paths to individual API items. + * For example, if the `projectFolderUrl` is "https://github.com/microsoft/rushstack/tree/main/apps/api-extractor" and an API + * item's file path is "api/ExtractorConfig.ts", the full URL file path would be + * "https://github.com/microsoft/rushstack/tree/main/apps/api-extractor/api/ExtractorConfig.js". + * + * This setting can be omitted if you don't need source code links in your API documentation reference. + * + * SUPPORTED TOKENS: none + * DEFAULT VALUE: "" + */ + // "projectFolderUrl": "http://github.com/path/to/your/projectFolder" + }, + /** + * Configures how the .d.ts rollup file will be generated. + */ + "dtsRollup": { + /** + * (REQUIRED) Whether to generate the .d.ts rollup file. + */ + "enabled": true + /** + * Specifies the output path for a .d.ts rollup file to be generated without any trimming. + * This file will include all declarations that are exported by the main entry point. + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/dist/.d.ts" + */ + // "untrimmedFilePath": "/dist/.d.ts", + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for an "alpha" release. + * This file will include only declarations that are marked as "@public", "@beta", or "@alpha". + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "alphaTrimmedFilePath": "/dist/-alpha.d.ts", + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release. + * This file will include only declarations that are marked as "@public" or "@beta". + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "betaTrimmedFilePath": "/dist/-beta.d.ts", + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release. + * This file will include only declarations that are marked as "@public". + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "publicTrimmedFilePath": "/dist/-public.d.ts", + /** + * When a declaration is trimmed, by default it will be replaced by a code comment such as + * "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the + * declaration completely. + * + * DEFAULT VALUE: false + */ + // "omitTrimmingComments": true + }, + /** + * Configures how the tsdoc-metadata.json file will be generated. + */ + "tsdocMetadata": { + /** + * Whether to generate the tsdoc-metadata.json file. + * + * DEFAULT VALUE: true + */ + // "enabled": true, + /** + * Specifies where the TSDoc metadata file should be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * The default value is "", which causes the path to be automatically inferred from the "tsdocMetadata", + * "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup + * falls back to "tsdoc-metadata.json" in the package folder. + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json" + }, + /** + * Configures how API Extractor reports error and warning messages produced during analysis. + * + * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages. + */ + "messages": { + /** + * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing + * the input .d.ts files. + * + * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551" + * + * DEFAULT VALUE: A single "default" entry with logLevel=warning. + */ + "compilerMessageReporting": { + /** + * Configures the default routing for messages that don't match an explicit rule in this table. + */ + "default": { + /** + * Specifies whether the message should be written to the the tool's output log. Note that + * the "addToApiReportFile" property may supersede this option. + * + * Possible values: "error", "warning", "none" + * + * Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail + * and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes + * the "--local" option), the warning is displayed but the build will not fail. + * + * DEFAULT VALUE: "warning" + */ + "logLevel": "warning" + /** + * When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md), + * then the message will be written inside that file; otherwise, the message is instead logged according to + * the "logLevel" option. + * + * DEFAULT VALUE: false + */ + // "addToApiReportFile": false + } + // "TS2551": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + }, + /** + * Configures handling of messages reported by API Extractor during its analysis. + * + * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag" + * + * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings + */ + "extractorMessageReporting": { + "default": { + "logLevel": "warning" + // "addToApiReportFile": false + } + // "ae-extra-release-tag": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + }, + /** + * Configures handling of messages reported by the TSDoc parser when analyzing code comments. + * + * TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text" + * + * DEFAULT VALUE: A single "default" entry with logLevel=warning. + */ + "tsdocMessageReporting": { + "default": { + "logLevel": "warning" + // "addToApiReportFile": false + } + // "tsdoc-link-tag-unescaped-text": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + } + } +} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..bc0a6ab --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,33 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default [ + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + ignores: [ + // Ignore built files. + 'dist/**', + ], + }, + { + rules: { + '@typescript-eslint/no-unused-vars': [ + 'error', + { + 'argsIgnorePattern': '^_', + 'varsIgnorePattern': '^_', + }, + ], + '@typescript-eslint/no-empty-object-type': [ + 'error', + { + 'allowInterfaces': 'always', + }, + ], + }, + }, +]; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..145bfc4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4491 @@ +{ + "name": "agntcy-dir", + "version": "1.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agntcy-dir", + "version": "1.1.0", + "license": "Apache-2.0", + "dependencies": { + "@buf/agntcy_dir.bufbuild_es": "^2.9.0-20260319131759-f3a65f0dc151.1", + "@buf/bufbuild_protovalidate.bufbuild_es": "^2.11.0-20260209202127-80ab13bee0bf.1", + "@bufbuild/protobuf": "^2.9.0", + "@connectrpc/connect": "^2.1.0", + "@connectrpc/connect-node": "^2.1.0", + "@grpc/grpc-js": "^1.13.4", + "spiffe": "^0.5.0" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.58.0", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.1", + "@types/node": "^22.19.1", + "@types/uuid": "^10.0.0", + "rollup-plugin-typescript2": "^0.37.0", + "ts-node": "^10.9.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.58.0", + "uuid": "^11.1.0", + "vitest": "^3.2.4", + "workerpool": "^10.0.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.60.1" + } + }, + "node_modules/@buf/agntcy_dir.bufbuild_es": { + "version": "2.9.0-20260319131759-f3a65f0dc151.1", + "resolved": "https://buf.build/gen/npm/v1/@buf/agntcy_dir.bufbuild_es/-/agntcy_dir.bufbuild_es-2.9.0-20260319131759-f3a65f0dc151.1.tgz", + "dependencies": { + "@buf/bufbuild_protovalidate.bufbuild_es": "2.9.0-20260209202127-80ab13bee0bf.1" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^2.9.0" + } + }, + "node_modules/@buf/agntcy_dir.bufbuild_es/node_modules/@buf/bufbuild_protovalidate.bufbuild_es": { + "version": "2.9.0-20260209202127-80ab13bee0bf.1", + "resolved": "https://buf.build/gen/npm/v1/@buf/bufbuild_protovalidate.bufbuild_es/-/bufbuild_protovalidate.bufbuild_es-2.9.0-20260209202127-80ab13bee0bf.1.tgz", + "peerDependencies": { + "@bufbuild/protobuf": "^2.9.0" + } + }, + "node_modules/@buf/bufbuild_protovalidate.bufbuild_es": { + "version": "2.11.0-20260209202127-80ab13bee0bf.1", + "resolved": "https://buf.build/gen/npm/v1/@buf/bufbuild_protovalidate.bufbuild_es/-/bufbuild_protovalidate.bufbuild_es-2.11.0-20260209202127-80ab13bee0bf.1.tgz", + "peerDependencies": { + "@bufbuild/protobuf": "^2.11.0" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", + "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@connectrpc/connect": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.1.1.tgz", + "integrity": "sha512-JzhkaTvM73m2K1URT6tv53k2RwngSmCXLZJgK580qNQOXRzZRR/BCMfZw3h+90JpnG6XksP5bYT+cz0rpUzUWQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0" + } + }, + "node_modules/@connectrpc/connect-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-2.1.1.tgz", + "integrity": "sha512-s3TfsI1XF+n+1z6MBS9rTnFsxxR4Rw5wmdEnkQINli81ESGxcsfaEet8duzq8LVuuCupmhUsgpRo0Nv9pZkufg==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0", + "@connectrpc/connect": "2.1.1" + } + }, + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.3", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz", + "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/object-schema": "^3.0.3", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz", + "integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^1.1.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", + "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz", + "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz", + "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^1.1.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@microsoft/api-extractor": { + "version": "7.58.0", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.58.0.tgz", + "integrity": "sha512-XcHlDylX5GblbGbs1eBoexjVyvdJMioTPuBCgCorE2rqijzTYxi6eudXyez3xACRxtH9aDtahoL9fYM4XTvQmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/api-extractor-model": "7.33.5", + "@microsoft/tsdoc": "~0.16.0", + "@microsoft/tsdoc-config": "~0.18.1", + "@rushstack/node-core-library": "5.21.0", + "@rushstack/rig-package": "0.7.2", + "@rushstack/terminal": "0.22.4", + "@rushstack/ts-command-line": "5.3.4", + "diff": "~8.0.2", + "lodash": "~4.17.23", + "minimatch": "10.2.3", + "resolve": "~1.22.1", + "semver": "~7.5.4", + "source-map": "~0.6.1", + "typescript": "5.9.3" + }, + "bin": { + "api-extractor": "bin/api-extractor" + } + }, + "node_modules/@microsoft/api-extractor-model": { + "version": "7.33.5", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.33.5.tgz", + "integrity": "sha512-Xh4dXuusndVQqVz4nEN9xOp0DyzsKxeD2FFJkSPg4arAjDSKPcy6cAc7CaeBPA7kF2wV1fuDlo2p/bNMpVr8yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "~0.16.0", + "@microsoft/tsdoc-config": "~0.18.1", + "@rushstack/node-core-library": "5.21.0" + } + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", + "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.18.1.tgz", + "integrity": "sha512-9brPoVdfN9k9g0dcWkFeA7IH9bbcttzDJlXvkf8b2OBzd5MueR1V2wkKBL0abn0otvmkHJC6aapBOTJDDeMCZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.16.0", + "ajv": "~8.18.0", + "jju": "~1.4.0", + "resolve": "~1.22.2" + } + }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz", + "integrity": "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz", + "integrity": "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz", + "integrity": "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz", + "integrity": "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-rsa": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz", + "integrity": "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz", + "integrity": "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pfx": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz", + "integrity": "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz", + "integrity": "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz", + "integrity": "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", + "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", + "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.8.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@protobuf-ts/grpc-transport": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/grpc-transport/-/grpc-transport-2.11.1.tgz", + "integrity": "sha512-l6wrcFffY+tuNnuyrNCkRM8hDIsAZVLA8Mn7PKdVyYxITosYh60qW663p9kL6TWXYuDCL3oxH8ih3vLKTDyhtg==", + "license": "Apache-2.0", + "dependencies": { + "@protobuf-ts/runtime": "^2.11.1", + "@protobuf-ts/runtime-rpc": "^2.11.1" + }, + "peerDependencies": { + "@grpc/grpc-js": "^1.6.0" + } + }, + "node_modules/@protobuf-ts/runtime": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.11.1.tgz", + "integrity": "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@protobuf-ts/runtime-rpc": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.11.1.tgz", + "integrity": "sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ==", + "license": "Apache-2.0", + "dependencies": { + "@protobuf-ts/runtime": "^2.11.1" + } + }, + "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==", + "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==", + "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==", + "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==", + "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" + } + }, + "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==", + "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==", + "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==", + "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==", + "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==", + "license": "BSD-3-Clause" + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rushstack/node-core-library": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.21.0.tgz", + "integrity": "sha512-LFzN+1lyWROit/P8Md6yxAth7lLYKn37oCKJHirEE2TQB25NDUM7bALf0ar+JAtwFfRCH+D+DGOA7DAzIi2r+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "~8.18.0", + "ajv-draft-04": "~1.0.0", + "ajv-formats": "~3.0.1", + "fs-extra": "~11.3.0", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.22.1", + "semver": "~7.5.4" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/problem-matcher": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.2.1.tgz", + "integrity": "sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/rig-package": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.7.2.tgz", + "integrity": "sha512-9XbFWuqMYcHUso4mnETfhGVUSaADBRj6HUAAEYk50nMPn8WRICmBuCphycQGNB3duIR6EEZX3Xj3SYc2XiP+9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "~1.22.1", + "strip-json-comments": "~3.1.1" + } + }, + "node_modules/@rushstack/terminal": { + "version": "0.22.4", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.22.4.tgz", + "integrity": "sha512-fhtLjnXCc/4WleVbVl6aoc7jcWnU6yqjS1S8WoaNREG3ycu/viZ9R/9QM7Y/b4CDvcXoiDyMNIay7JMwBptM3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rushstack/node-core-library": "5.21.0", + "@rushstack/problem-matcher": "0.2.1", + "supports-color": "~8.1.1" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/ts-command-line": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.3.4.tgz", + "integrity": "sha512-MLkVKVEN6/2clKTrjN2B2KqKCuPxRwnNsWY7a+FCAq2EMdkj10cM8YgiBSMeGFfzM0mDMzargpHNnNzaBi9Whg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rushstack/terminal": "0.22.4", + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "string-argv": "~0.3.1" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "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, + "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, + "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, + "license": "MIT" + }, + "node_modules/@types/argparse": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", + "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", + "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/type-utils": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.58.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", + "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", + "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.0", + "@typescript-eslint/types": "^8.58.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", + "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", + "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", + "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", + "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", + "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.58.0", + "@typescript-eslint/tsconfig-utils": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", + "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", + "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-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, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/asn1js": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", + "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-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", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.3.tgz", + "integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.3", + "@eslint/config-helpers": "^0.5.2", + "@eslint/core": "^1.1.1", + "@eslint/plugin-kit": "^0.6.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.1.1", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/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", + "peer": true + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-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", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "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", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/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/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "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, + "license": "ISC", + "peer": true + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/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, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/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, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/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, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/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, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-typescript2": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.37.0.tgz", + "integrity": "sha512-S1r/4Ufi13Yg/chPlh4iSHWq2Zs/sIAodW5SKUoCQfy/DEQhkS2XRFEtv+NRq3iBO4WHHfqKtDPOC5lJTYm7OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^4.1.2", + "find-cache-dir": "^3.3.2", + "fs-extra": "^10.0.0", + "semver": "^7.5.4", + "tslib": "^2.6.2" + }, + "peerDependencies": { + "rollup": ">=1.26.3", + "typescript": ">=2.4.0" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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", + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spiffe": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/spiffe/-/spiffe-0.5.1.tgz", + "integrity": "sha512-oCHXpMDo0WFXDXPKl6h7pzA1ub6xzp4fnsAEvJwydEeWjIaJsT0lCB06ix8KvIgAcyRZ06rejCjBxRXmaxhn3w==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.14.3", + "@peculiar/webcrypto": "^1.5.0", + "@peculiar/x509": "^1.14.3", + "@protobuf-ts/grpc-transport": "^2.11.1", + "@protobuf-ts/runtime": "^2.11.1", + "@protobuf-ts/runtime-rpc": "^2.11.1" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "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, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "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", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", + "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.0", + "@typescript-eslint/parser": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "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" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/webcrypto-core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", + "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/json-schema": "^1.1.12", + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.7.0" + } + }, + "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", + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "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", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerpool": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-10.0.1.tgz", + "integrity": "sha512-NAnKwZJxWlj/U1cp6ZkEtPE+GQY1S6KtOS3AlCiPfPFLxV3m64giSp7g2LsNJxzYCocDT7TSl+7T0sgrDp3KoQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "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/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..63811f2 --- /dev/null +++ b/package.json @@ -0,0 +1,82 @@ +{ + "name": "agntcy-dir", + "version": "1.1.0", + "description": "Directory SDK", + "homepage": "https://github.com/agntcy/dir", + "bugs": { + "url": "https://github.com/agntcy/dir/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/agntcy/dir.git" + }, + "license": "Apache-2.0", + "keywords": [ + "sdk", + "client", + "directory", + "agntcy", + "typescript", + "javascript" + ], + "type": "module", + "main": "dist/index.mjs", + "module": "dist/index.mjs", + "typings": "dist/agntcy-dir.d.ts", + "exports": { + ".": { + "types": "./dist/agntcy-dir.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs", + "default": "./dist/index.mjs" + } + }, + "scripts": { + "build": "rollup -c && api-extractor run --local --verbose --diagnostics", + "clean": "rm -rf dist", + "lint": "eslint '**/*.ts'", + "fix": "eslint '**/*.ts' --fix", + "pretest": "npm run build", + "test": "vitest run" + }, + "packageManager": "npm@11.12.1", + "dependencies": { + "@buf/agntcy_dir.bufbuild_es": "^2.9.0-20260319131759-f3a65f0dc151.1", + "@buf/bufbuild_protovalidate.bufbuild_es": "^2.11.0-20260209202127-80ab13bee0bf.1", + "@bufbuild/protobuf": "^2.9.0", + "@connectrpc/connect": "^2.1.0", + "@connectrpc/connect-node": "^2.1.0", + "@grpc/grpc-js": "^1.13.4", + "spiffe": "^0.5.0" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.58.0", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.1", + "@types/node": "^22.19.1", + "@types/uuid": "^10.0.0", + "rollup-plugin-typescript2": "^0.37.0", + "ts-node": "^10.9.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.58.0", + "uuid": "^11.1.0", + "vitest": "^3.2.4", + "workerpool": "^10.0.1" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.60.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "files": [ + "dist/agntcy-dir.d.ts", + "dist/index.mjs", + "dist/index.cjs", + "dist/index.mjs.map" + ], + "directories": { + "test": "test" + }, + "author": "" +} diff --git a/rollup.config.mjs b/rollup.config.mjs new file mode 100644 index 0000000..44b4c24 --- /dev/null +++ b/rollup.config.mjs @@ -0,0 +1,53 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +import json from '@rollup/plugin-json'; +import { readFileSync } from 'fs'; +import typescript from 'rollup-plugin-typescript2'; +import { nodeResolve } from '@rollup/plugin-node-resolve'; + +const pkg = JSON.parse( + readFileSync(new URL('./package.json', import.meta.url), 'utf8'), +); + +const rollupPlugins = [ + nodeResolve(), + typescript({ + tsconfigOverride: { + exclude: ['test/**'], + }, + }), + json({ + preferConst: true, + }), +]; + +const externalPackages = [ + 'spiffe', +]; + +export default [ + // Cross ES module (dist/index.mjs) + { + input: 'src/index.ts', + output: { + file: pkg.exports['.']['import'], + format: 'es', + sourcemap: true, + }, + plugins: rollupPlugins, + external: externalPackages, + }, + + // Cross CJS module (dist/index.cjs) + { + input: 'src/index.ts', + output: { + file: pkg.exports['.']['require'], + format: 'cjs', + sourcemap: true, + }, + plugins: rollupPlugins, + external: externalPackages, + }, +]; diff --git a/src/client/client.ts b/src/client/client.ts new file mode 100644 index 0000000..12c3dcb --- /dev/null +++ b/src/client/client.ts @@ -0,0 +1,1758 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +import { readFileSync, mkdtempSync, rmSync, existsSync, openSync, closeSync } from 'node:fs'; +import type * as http2 from 'node:http2'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { env } from 'node:process'; +import { spawnSync, SpawnSyncReturns } from 'node:child_process'; + +import { + Client as GrpcClient, + createClient, + Interceptor, + Transport, +} from '@connectrpc/connect'; +import { createGrpcTransport } from '@connectrpc/connect-node'; +import { createClient as createClientSpiffe, X509SVID } from 'spiffe'; +import { fromJsonString } from '@bufbuild/protobuf'; +import * as models from '../models'; + +import { + fetchOpenidConfiguration, + OAuthTokenHolder, + runLoopbackPkceLogin, +} from './oauthPkce.js'; +import { CachedToken, TokenCache } from './tokenCache.js'; + +export { CachedToken, TokenCache, TOKEN_CACHE_FILE } from './tokenCache.js'; +export { OAuthPkceError, OAuthTokenHolder } from './oauthPkce.js'; + +export type AuthMode = '' | 'x509' | 'jwt' | 'tls' | 'oidc'; + +function parseBoolEnv(value: string | undefined, defaultVal: boolean): boolean { + if (value === undefined || value === '') { + return defaultVal; + } + return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase()); +} + +function parseIntEnv(value: string | undefined, defaultVal: number): number { + if (value === undefined || value === '') { + return defaultVal; + } + return Number.parseInt(value, 10); +} + +function parseFloatEnv(value: string | undefined, defaultVal: number): number { + if (value === undefined || value === '') { + return defaultVal; + } + return Number.parseFloat(value); +} + +function parseCommaScopes(value: string | undefined, defaultList: string[]): string[] { + if (value === undefined || value === '') { + return [...defaultList]; + } + return value + .split(',') + .map((s) => s.trim()) + .filter(Boolean); +} + +/** + * Configuration class for the AGNTCY Directory client. + * + * This class manages configuration settings for connecting to the Directory service + * and provides default values and environment-based configuration loading. + */ +export class Config { + static DEFAULT_SERVER_ADDRESS = '127.0.0.1:8888'; + static DEFAULT_DIRCTL_PATH = 'dirctl'; + static DEFAULT_SPIFFE_ENDPOINT_SOCKET = ''; + static DEFAULT_AUTH_MODE = ''; + static DEFAULT_AUTH_TOKEN = ''; + static DEFAULT_JWT_AUDIENCE = ''; + static DEFAULT_TLS_CA_FILE = ''; + static DEFAULT_TLS_CERT_FILE = ''; + static DEFAULT_TLS_KEY_FILE = ''; + static DEFAULT_TLS_SERVER_NAME = ''; + static DEFAULT_TLS_SKIP_VERIFY = false; + static DEFAULT_OIDC_ISSUER = ''; + static DEFAULT_OIDC_CLIENT_ID = ''; + static DEFAULT_OIDC_CLIENT_SECRET = ''; + static DEFAULT_OIDC_REDIRECT_URI = 'http://localhost:8484/callback'; + static DEFAULT_OIDC_CALLBACK_PORT = 8484; + static DEFAULT_OIDC_AUTH_TIMEOUT = 300; + static DEFAULT_OIDC_SCOPES = ['openid', 'profile', 'email']; + + serverAddress: string; + dirctlPath: string; + spiffeEndpointSocket: string; + authMode: AuthMode; + authToken: string; + /** Backward-compatible alias for `authToken`. */ + oidcAccessToken: string; + jwtAudience: string; + tlsCaFile: string; + tlsCertFile: string; + tlsKeyFile: string; + tlsServerName: string; + tlsSkipVerify: boolean; + oidcIssuer: string; + oidcClientId: string; + oidcClientSecret: string; + oidcRedirectUri: string; + oidcCallbackPort: number; + oidcAuthTimeout: number; + oidcScopes: string[]; + dockerConfig: DockerConfig | undefined; + + /** + * Creates a new Config instance. + * + * @param serverAddress - The server address to connect to. Defaults to '127.0.0.1:8888' + * @param dirctlPath - Path to the dirctl executable. Defaults to 'dirctl' + * @param spiffeEndpointSocket - Path to the spire server socket. Defaults to empty string. + * @param authMode - Authentication mode: '' for insecure, 'x509', 'jwt', 'tls', or 'oidc'. Defaults to '' + * @param jwtAudience - JWT audience for JWT authentication. Required when authMode is 'jwt' + */ + constructor( + serverAddress = Config.DEFAULT_SERVER_ADDRESS, + dirctlPath = Config.DEFAULT_DIRCTL_PATH, + spiffeEndpointSocket = Config.DEFAULT_SPIFFE_ENDPOINT_SOCKET, + authMode: AuthMode = Config.DEFAULT_AUTH_MODE as AuthMode, + jwtAudience = Config.DEFAULT_JWT_AUDIENCE, + tlsCaFile = Config.DEFAULT_TLS_CA_FILE, + tlsCertFile = Config.DEFAULT_TLS_CERT_FILE, + tlsKeyFile = Config.DEFAULT_TLS_KEY_FILE, + authToken = Config.DEFAULT_AUTH_TOKEN, + tlsServerName = Config.DEFAULT_TLS_SERVER_NAME, + tlsSkipVerify = Config.DEFAULT_TLS_SKIP_VERIFY, + oidcIssuer = Config.DEFAULT_OIDC_ISSUER, + oidcClientId = Config.DEFAULT_OIDC_CLIENT_ID, + oidcClientSecret = Config.DEFAULT_OIDC_CLIENT_SECRET, + oidcRedirectUri = Config.DEFAULT_OIDC_REDIRECT_URI, + oidcCallbackPort = Config.DEFAULT_OIDC_CALLBACK_PORT, + oidcAuthTimeout = Config.DEFAULT_OIDC_AUTH_TIMEOUT, + oidcScopes: string[] | undefined = undefined, + oidcAccessToken: string | undefined = undefined, + dockerConfig: DockerConfig | undefined = undefined, + ) { + const resolvedAuthToken = authToken || oidcAccessToken || ''; + + // add protocol prefix if not set + // use unsafe http unless spire/auth is used + if ( + !serverAddress.startsWith('http://') && + !serverAddress.startsWith('https://') + ) { + // use https protocol when X.509, JWT, TLS, or OIDC auth is used + if ( + authMode === 'x509' || + authMode === 'jwt' || + authMode === 'tls' || + authMode === 'oidc' + ) { + serverAddress = `https://${serverAddress}`; + } else { + serverAddress = `http://${serverAddress}`; + } + } + + this.serverAddress = serverAddress; + this.dirctlPath = dirctlPath; + this.spiffeEndpointSocket = spiffeEndpointSocket; + this.authMode = authMode; + this.authToken = resolvedAuthToken; + this.oidcAccessToken = resolvedAuthToken; + this.jwtAudience = jwtAudience; + this.tlsCaFile = tlsCaFile; + this.tlsCertFile = tlsCertFile; + this.tlsKeyFile = tlsKeyFile; + this.tlsServerName = tlsServerName; + this.tlsSkipVerify = tlsSkipVerify; + this.oidcIssuer = oidcIssuer; + this.oidcClientId = oidcClientId; + this.oidcClientSecret = oidcClientSecret; + this.oidcRedirectUri = oidcRedirectUri; + this.oidcCallbackPort = oidcCallbackPort; + this.oidcAuthTimeout = oidcAuthTimeout; + this.oidcScopes = + oidcScopes !== undefined ? [...oidcScopes] : [...Config.DEFAULT_OIDC_SCOPES]; + this.dockerConfig = dockerConfig; + } + + /** + * Load configuration from environment variables. + * + * @param prefix - Environment variable prefix. Defaults to 'DIRECTORY_CLIENT_' + * @returns A new Config instance with values loaded from environment variables + * + * @example + * ```typescript + * // Load with default prefix + * const config = Config.loadFromEnv(); + * + * // Load with custom prefix + * const config = Config.loadFromEnv("MY_APP_"); + * ``` + */ + static loadFromEnv(prefix = 'DIRECTORY_CLIENT_') { + // Load dirctl path from env without env prefix + const dirctlPath = env['DIRCTL_PATH'] || Config.DEFAULT_DIRCTL_PATH; + + // Load other config values with env prefix + const serverAddress = + env[`${prefix}SERVER_ADDRESS`] || Config.DEFAULT_SERVER_ADDRESS; + const spiffeEndpointSocketPath = + env[`${prefix}SPIFFE_SOCKET_PATH`] || Config.DEFAULT_SPIFFE_ENDPOINT_SOCKET; + const authMode = (env[`${prefix}AUTH_MODE`] || Config.DEFAULT_AUTH_MODE) as AuthMode; + const authToken = env[`${prefix}AUTH_TOKEN`] || Config.DEFAULT_AUTH_TOKEN; + const jwtAudience = env[`${prefix}JWT_AUDIENCE`] || Config.DEFAULT_JWT_AUDIENCE; + const tlsCaFile = env[`${prefix}TLS_CA_FILE`] || Config.DEFAULT_TLS_CA_FILE; + const tlsCertFile = env[`${prefix}TLS_CERT_FILE`] || Config.DEFAULT_TLS_CERT_FILE; + const tlsKeyFile = env[`${prefix}TLS_KEY_FILE`] || Config.DEFAULT_TLS_KEY_FILE; + const tlsServerName = + env[`${prefix}TLS_SERVER_NAME`] || Config.DEFAULT_TLS_SERVER_NAME; + const tlsSkipVerify = parseBoolEnv( + env[`${prefix}TLS_SKIP_VERIFY`], + Config.DEFAULT_TLS_SKIP_VERIFY, + ); + const oidcIssuer = env[`${prefix}OIDC_ISSUER`] || Config.DEFAULT_OIDC_ISSUER; + const oidcClientId = + env[`${prefix}OIDC_CLIENT_ID`] || Config.DEFAULT_OIDC_CLIENT_ID; + const oidcClientSecret = + env[`${prefix}OIDC_CLIENT_SECRET`] || Config.DEFAULT_OIDC_CLIENT_SECRET; + const oidcRedirectUri = + env[`${prefix}OIDC_REDIRECT_URI`] || Config.DEFAULT_OIDC_REDIRECT_URI; + const oidcCallbackPort = parseIntEnv( + env[`${prefix}OIDC_CALLBACK_PORT`], + Config.DEFAULT_OIDC_CALLBACK_PORT, + ); + const oidcAuthTimeout = parseFloatEnv( + env[`${prefix}OIDC_AUTH_TIMEOUT`], + Config.DEFAULT_OIDC_AUTH_TIMEOUT, + ); + const oidcScopes = parseCommaScopes( + env[`${prefix}OIDC_SCOPES`], + Config.DEFAULT_OIDC_SCOPES, + ); + + var dockerConfig = undefined; + const dirctlImage = env["DIRCTL_IMAGE"] + const dirctlImageTag = env["DIRCTL_IMAGE_TAG"] + if (dirctlImage || dirctlImageTag) { + dockerConfig = new DockerConfig( + dirctlImage || DockerConfig.DEFAULT_DIRCTL_IMAGE, + dirctlImageTag || DockerConfig.DEFAULT_DIRCTL_IMAGE_TAG, + new Map, + [], + "0:0", + ) + } + + return new Config( + serverAddress, + dirctlPath, + spiffeEndpointSocketPath, + authMode, + jwtAudience, + tlsCaFile, + tlsCertFile, + tlsKeyFile, + authToken, + tlsServerName, + tlsSkipVerify, + oidcIssuer, + oidcClientId, + oidcClientSecret, + oidcRedirectUri, + oidcCallbackPort, + oidcAuthTimeout, + oidcScopes, + undefined, + dockerConfig, + ); + } + + getCommandAndArgs(args: string[]): [string, string[]] { + if (this.dockerConfig) { + const dockerArgs = this.dockerConfig.getDockerArgs(); + return ["docker", dockerArgs.concat(args)]; + } else { + return [`${this.dirctlPath}`, args] + } + } +} + +class DockerConfig { + static DEFAULT_DIRCTL_IMAGE = 'ghcr.io/agntcy/dir-ctl'; + static DEFAULT_DIRCTL_IMAGE_TAG = 'latest'; + + dirctlImage: string; + dirctlImageTag: string; + envs: Map; + mounts: string[]; + user: string; + + constructor( + dirctlImage: string, + dirctlImageTag: string, + envs: Map, + mounts: string[], + user: string, + ) { + this.dirctlImage = dirctlImage; + this.dirctlImageTag = dirctlImageTag; + this.envs = envs; + this.mounts = mounts; + this.user = user; + } + + getDockerArgs(): string[] { + this.pruneMounts(); + const commands = ["container", "run", "--name=dir-ctl", "--rm", "--network", "host"] + if (this.user) { + commands.push("--user") + commands.push(this.user) + } + this.envs.forEach((value: string, key: string) => { + commands.push("--env") + commands.push(`${key}=${value}`) + }) + this.mounts.forEach((value: string) => { + commands.push("--mount") + commands.push(value) + }) + commands.push(`${this.dirctlImage}:${this.dirctlImageTag}`) + return commands + } + + pruneMounts(): void { + this.mounts = this.mounts.filter((mount: string) => { + if (mount.startsWith("type=bind")) { + const [type, src, dst] = mount.split(","); + const [_, _src] = src.split("="); + return existsSync(_src); + } + + return false; + }) + } +} + + +/** + * High-level client for interacting with AGNTCY Directory services. + * + * This client provides a unified interface for operations across the Directory API. + * It handles gRPC communication and provides convenient methods for common operations + * including storage, routing, search, signing, and synchronization. + * + * @example + * ```typescript + * // Create client with default configuration + * const client = new Client(); + * + * // Create client with custom configuration + * const config = new Config('localhost:8888', '/usr/local/bin/dirctl'); + * const client = new Client(config); + * + * // Use client for operations + * const records = await client.push([record]); + * ``` + */ +export class Client { + config: Config; + storeClient: GrpcClient; + routingClient: GrpcClient; + publicationClient: GrpcClient; + searchClient: GrpcClient; + signClient: GrpcClient; + syncClient: GrpcClient; + eventClient: GrpcClient; + namingClient: GrpcClient; + + private oauthHolder: OAuthTokenHolder | null = null; + + /** + * Initialize the client with the given configuration. + * + * @param config - Optional client configuration. If null, loads from environment + * variables using Config.loadFromEnv() + * @param grpcTransport - Optional transport to use for gRPC communication. + * Can be created with Client.createGRPCTransport(config) + * + * @throws {Error} If unable to establish connection to the server or configuration is invalid + * + * @example + * ```typescript + * // Load config from environment + * const client = new Client(); + * + * // Use custom config + * const config = new Config('localhost:9999'); + * const grpcTransport = await Client.createGRPCTransport(config); + * const client = new Client(config, grpcTransport); + * ``` + */ + constructor(); + constructor(config?: Config); + constructor(config?: Config, grpcTransport?: Transport); + constructor(config?: Config, grpcTransport?: Transport) { + // Load config from environment if not provided + if (!config) { + config = Config.loadFromEnv(); + } + this.config = config; + + if (config.authMode === 'oidc') { + this.oauthHolder = new OAuthTokenHolder(); + if (config.authToken) { + this.oauthHolder.setTokens(config.authToken); + } else { + const cachedToken = new TokenCache().getValidToken(); + if (cachedToken !== undefined) { + this.oauthHolder.setTokens(cachedToken.accessToken); + } + } + } + + // if no transport provided, use insecure transport (or OIDC transport) + if (!grpcTransport) { + if (config.authMode === 'oidc') { + if (this.oauthHolder === null) { + throw new Error('OAuth token holder not initialized'); + } + grpcTransport = Client.createOidcTransport(config, this.oauthHolder); + } else { + grpcTransport = createGrpcTransport({ + baseUrl: config.serverAddress, + }); + } + } + + // Set clients for all services + this.storeClient = createClient(models.store_v1.StoreService, grpcTransport); + this.routingClient = createClient( + models.routing_v1.RoutingService, + grpcTransport, + ); + this.publicationClient = createClient(models.routing_v1.PublicationService, grpcTransport); + this.searchClient = createClient(models.search_v1.SearchService, grpcTransport); + this.signClient = createClient(models.sign_v1.SignService, grpcTransport); + this.syncClient = createClient(models.store_v1.SyncService, grpcTransport); + this.eventClient = createClient(models.events_v1.EventService, grpcTransport); + this.namingClient = createClient(models.naming_v1.NamingService, grpcTransport); + } + + private static convertToPEM(bytes: Uint8Array, label: string): string { + // Convert Uint8Array to base64 string + let binary = ''; + const len = bytes.byteLength; + for (let i = 0; i < len; i++) { + binary += String.fromCharCode(bytes[i]); + } + const base64String = btoa(binary); + + // Split base64 string into 64-character lines + const lines = base64String.match(/.{1,64}/g) || []; + + // Build PEM formatted string with headers and footers + const pem = [ + `-----BEGIN ${label}-----`, + ...lines, + `-----END ${label}-----` + ].join('\n'); + + return pem; + } + + private static secureNodeOptions( + config: Config, + base: http2.SecureClientSessionOptions, + ): http2.SecureClientSessionOptions { + const out: http2.SecureClientSessionOptions = { ...base }; + const sn = config.tlsServerName.trim(); + if (sn !== '') { + out.servername = sn; + } + return out; + } + + private static createOidcTransport(config: Config, holder: OAuthTokenHolder): Transport { + let ca: string | undefined; + if (config.tlsCaFile !== '') { + try { + ca = readFileSync(config.tlsCaFile).toString(); + } catch (e) { + throw new Error(`Failed to read TLS CA file: ${(e as Error).message}`); + } + } + const nodeBase: http2.SecureClientSessionOptions = {}; + if (ca !== undefined) { + nodeBase.ca = ca; + } + const bearerInterceptor: Interceptor = (next) => async (req) => { + req.header.set('authorization', `Bearer ${holder.getAccessToken()}`); + return await next(req); + }; + return createGrpcTransport({ + baseUrl: config.serverAddress, + interceptors: [bearerInterceptor], + nodeOptions: Client.secureNodeOptions(config, nodeBase), + }); + } + + static async createGRPCTransport( + config: Config, + oidcOptions?: { oidcTokenHolder: OAuthTokenHolder }, + ): Promise { + // Handle different authentication modes + switch (config.authMode) { + case '': + return createGrpcTransport({ + baseUrl: config.serverAddress, + }); + + case 'jwt': + return await this.createJWTTransport(config); + + case 'x509': + return await this.createX509Transport(config); + + case 'tls': + return await this.createTLSTransport(config); + + case 'oidc': { + const holder = oidcOptions?.oidcTokenHolder; + if (holder === undefined) { + throw new Error( + 'createGRPCTransport: authMode oidc requires options.oidcTokenHolder', + ); + } + return Client.createOidcTransport(config, holder); + } + + default: + throw new Error(`Unsupported auth mode: ${config.authMode}`); + } + } + + private static async createX509Transport(config: Config): Promise { + if (config.spiffeEndpointSocket === '') { + throw new Error('SPIFFE socket path is required for X.509 authentication'); + } + + // Create secure transport with SPIFFE X.509 + const client = createClientSpiffe(config.spiffeEndpointSocket); + + let svid: X509SVID = { + spiffeId: '', + hint: '', + x509Svid: new Uint8Array(), + x509SvidKey: new Uint8Array(), + bundle: new Uint8Array(), + }; + + const svidStream = client.fetchX509SVID({}); + for await (const message of svidStream.responses) { + message.svids.forEach((_svid) => { + svid = _svid; + }) + + if (message.svids.length > 0) { + break + } + } + + // Create transport settings for gRPC client + const transport = createGrpcTransport({ + baseUrl: config.serverAddress, + nodeOptions: Client.secureNodeOptions(config, { + ca: this.convertToPEM(svid.bundle, "TRUSTED CERTIFICATE"), + cert: this.convertToPEM(svid.x509Svid, "CERTIFICATE"), + key: this.convertToPEM(svid.x509SvidKey, "PRIVATE KEY"), + }), + }); + + return transport; + } + + private static async createJWTTransport(config: Config): Promise { + if (config.spiffeEndpointSocket === '') { + throw new Error('SPIFFE socket path is required for JWT authentication'); + } + + if (config.jwtAudience === '') { + throw new Error('JWT audience is required for JWT authentication'); + } + + // Create SPIFFE client + const client = createClientSpiffe(config.spiffeEndpointSocket); + + // Fetch X.509 bundle for verifying server's TLS certificate + // In JWT mode, the server presents its X.509-SVID via TLS for transport security + let bundle: Uint8Array | null = null; + const bundleStream = client.fetchX509Bundles({}); + for await (const message of bundleStream.responses) { + // Get the first bundle from the bundles map + // bundles is a map where bytes is ASN.1 DER encoded + for (const [_, bundleData] of Object.entries(message.bundles)) { + // Convert to a new Uint8Array to ensure type compatibility + bundle = new Uint8Array(bundleData); + break; + } + if (bundle !== null) { + break; + } + } + + if (bundle === null || bundle.length === 0) { + throw new Error('Failed to fetch X.509 bundle from SPIRE: no bundles returned'); + } + + // Create JWT interceptor that fetches and injects JWT tokens + const jwtInterceptor: Interceptor = (next) => async (req) => { + // Fetch JWT-SVID from SPIRE + // Note: spiffeId is empty string to use the workload's default identity + const jwtCall = client.fetchJWTSVID({ + spiffeId: '', + audience: [config.jwtAudience] + }); + + const response = await jwtCall.response; + + if (!response.svids || response.svids.length === 0) { + throw new Error('Failed to fetch JWT-SVID from SPIRE: no SVIDs returned'); + } + + const jwtToken = response.svids[0].svid; + + // Add JWT token to request headers + req.header.set('authorization', `Bearer ${jwtToken}`); + + return await next(req); + }; + + // Create transport with JWT interceptor and TLS using SPIFFE bundle + // For JWT mode: Server presents X.509-SVID via TLS, clients authenticate with JWT-SVID + const transport = createGrpcTransport({ + baseUrl: config.serverAddress, + interceptors: [jwtInterceptor], + nodeOptions: Client.secureNodeOptions(config, { + ca: this.convertToPEM(bundle, "CERTIFICATE"), + }), + }); + + return transport; + } + + private static async createTLSTransport(config: Config): Promise { + if (config.tlsCaFile === '') { + throw new Error('TLS CA file is required for TLS authentication'); + } + if (config.tlsCertFile === '') { + throw new Error('TLS certificate file is required for TLS authentication'); + } + if (config.tlsKeyFile === '') { + throw new Error('TLS key file is required for TLS authentication'); + } + + let root_ca: string; + let cert_chain: string; + let private_key: string; + + try { + root_ca = readFileSync(config.tlsCaFile).toString(); + cert_chain = readFileSync(config.tlsCertFile).toString(); + private_key = readFileSync(config.tlsKeyFile).toString(); + } catch (e) { + console.error('Error reading file:', (e as Error).message); + throw e; + } + + const transport = createGrpcTransport({ + baseUrl: config.serverAddress, + nodeOptions: Client.secureNodeOptions(config, { + ca: root_ca, + cert: cert_chain, + key: private_key, + }), + }); + + return transport; + } + + private cachedTokenFromResponse(payload: Record): CachedToken { + const expiresIn = payload.expires_in; + let expiresAt: Date | undefined; + if (typeof expiresIn === 'number' && Number.isFinite(expiresIn)) { + expiresAt = new Date(Date.now() + expiresIn * 1000); + } else if (typeof expiresIn === 'string' && expiresIn !== '') { + const n = Number(expiresIn); + if (Number.isFinite(n)) { + expiresAt = new Date(Date.now() + n * 1000); + } + } + const refreshToken = payload.refresh_token; + const tokenType = payload.token_type; + return new CachedToken( + String(payload.access_token), + typeof tokenType === 'string' ? tokenType : '', + 'oidc', + this.config.oidcIssuer, + typeof refreshToken === 'string' ? refreshToken : '', + expiresAt, + '', + '', + '', + new Date(), + ); + } + + /** + * Run browser-based OAuth 2.0 Authorization Code + PKCE login (loopback callback). + * + * Requires `authMode: 'oidc'`, `oidcIssuer`, and `oidcClientId`. + */ + async authenticateOAuthPkce(): Promise { + if (this.config.authMode !== 'oidc') { + throw new Error("authenticateOAuthPkce() requires authMode='oidc'"); + } + if (this.config.oidcIssuer === '') { + throw new Error('oidc_issuer is required for authenticateOAuthPkce()'); + } + if (this.config.oidcClientId === '') { + throw new Error('oidc_client_id is required for authenticateOAuthPkce()'); + } + if (this.oauthHolder === null) { + throw new Error('OAuth token holder not initialized'); + } + const verify = !this.config.tlsSkipVerify; + const timeoutMs = Math.min(30_000, this.config.oidcAuthTimeout * 1000); + const meta = await fetchOpenidConfiguration(this.config.oidcIssuer, { + verify, + timeoutMs, + }); + const payload = await runLoopbackPkceLogin(this.config, meta, { + verify, + timeoutMs: this.config.oidcAuthTimeout * 1000, + }); + this.oauthHolder.updateFromTokenResponse(payload); + new TokenCache().save(this.cachedTokenFromResponse(payload)); + console.log('Authenticated with OAuth PKCE'); + console.log('Access token acquired.'); + } + + /** + * Request generator helper function for streaming requests. + */ + private async *requestGenerator(reqs: T[]): AsyncIterable { + for (const req of reqs) { + yield req; + } + } + + /** + * Push records to the Store API. + * + * Uploads one or more records to the content store, making them available + * for retrieval and reference. Each record is assigned a unique content + * identifier (CID) based on its content hash. + * + * @param records - Array of Record objects to push to the store + * @returns Promise that resolves to an array of RecordRef objects containing the CIDs of the pushed records + * + * @throws {Error} If the gRPC call fails or the push operation fails + * + * @example + * ```typescript + * const records = [createRecord("example")]; + * const refs = await client.push(records); + * console.log(`Pushed with CID: ${refs[0].cid}`); + * ``` + */ + async push( + records: models.core_v1.Record[], + ): Promise { + const responses: models.core_v1.RecordRef[] = []; + + for await (const response of this.storeClient.push( + this.requestGenerator(records), + )) { + responses.push(response); + } + + return responses; + } + + /** + * Push records with referrer metadata to the Store API. + * + * Uploads records along with optional artifacts and referrer information. + * This is useful for pushing complex objects that include additional + * metadata or associated artifacts. + * + * @param requests - Array of PushReferrerRequest objects containing records and optional artifacts + * @returns Promise that resolves to an array of PushReferrerResponse objects containing the details of pushed artifacts + * + * @throws {Error} If the gRPC call fails or the push operation fails + * + * @example + * ```typescript + * const requests = [new models.store_v1.PushReferrerRequest({record: record})]; + * const responses = await client.push_referrer(requests); + * ``` + */ + async push_referrer( + requests: models.store_v1.PushReferrerRequest[], + ): Promise { + const responses: models.store_v1.PushReferrerResponse[] = []; + + for await (const response of this.storeClient.pushReferrer( + this.requestGenerator(requests), + )) { + responses.push(response); + } + + return responses; + } + + /** + * Pull records from the Store API by their references. + * + * Retrieves one or more records from the content store using their + * content identifiers (CIDs). + * + * @param refs - Array of RecordRef objects containing the CIDs to retrieve + * @returns Promise that resolves to an array of Record objects retrieved from the store + * + * @throws {Error} If the gRPC call fails or the pull operation fails + * + * @example + * ```typescript + * const refs = [new models.core_v1.RecordRef({cid: "QmExample123"})]; + * const records = await client.pull(refs); + * for (const record of records) { + * console.log(`Retrieved record: ${record}`); + * } + * ``` + */ + async pull( + refs: models.core_v1.RecordRef[], + ): Promise { + const records: models.core_v1.Record[] = []; + + for await (const response of this.storeClient.pull( + this.requestGenerator(refs), + )) { + records.push(response); + } + + return records; + } + + /** + * Pull records with referrer metadata from the Store API. + * + * Retrieves records along with their associated artifacts and referrer + * information. This provides access to complex objects that include + * additional metadata or associated artifacts. + * + * @param requests - Array of PullReferrerRequest objects containing records and optional artifacts for pull operations + * @returns Promise that resolves to an array of PullReferrerResponse objects containing the retrieved records + * + * @throws {Error} If the gRPC call fails or the pull operation fails + * + * @example + * ```typescript + * const requests = [new models.store_v1.PullReferrerRequest({ref: ref})]; + * const responses = await client.pull_referrer(requests); + * for (const response of responses) { + * console.log(`Retrieved: ${response}`); + * } + * ``` + */ + async pull_referrer( + requests: models.store_v1.PullReferrerRequest[], + ): Promise { + const responses: models.store_v1.PullReferrerResponse[] = []; + + for await (const response of this.storeClient.pullReferrer( + this.requestGenerator(requests), + )) { + responses.push(response); + } + + return responses; + } + + /** + * Search objects from the Store API matching the specified queries. + * + * Performs a search across the storage using the provided search queries + * and returns a list of matching CIDs. This is efficient for lookups + * where only the CIDs are needed. + * + * @param request - SearchCIDsRequest containing queries, filters, and search options + * @returns Promise that resolves to an array of SearchCIDsResponse objects matching the queries + * + * @throws {Error} If the gRPC call fails or the search operation fails + * + * @example + * ```typescript + * const request = create(models.search_v1.SearchCIDsRequestSchema, {queries: [query], limit: 10}); + * const responses = await client.searchCIDs(request); + * for (const response of responses) { + * console.log(`Found CID: ${response.recordCid}`); + * } + * ``` + */ + async searchCIDs( + request: models.search_v1.SearchCIDsRequest, + ): Promise { + const responses: models.search_v1.SearchCIDsResponse[] = []; + + for await (const response of this.searchClient.searchCIDs(request)) { + responses.push(response); + } + + return responses; + } + + /** + * Search for full records from the Store API matching the specified queries. + * + * Performs a search across the storage using the provided search queries + * and returns a list of full records with all metadata. + * + * @param request - SearchRecordsRequest containing queries, filters, and search options + * @returns Promise that resolves to an array of SearchRecordsResponse objects matching the queries + * + * @throws {Error} If the gRPC call fails or the search operation fails + * + * @example + * ```typescript + * const request = create(models.search_v1.SearchRecordsRequestSchema, {queries: [query], limit: 10}); + * const responses = await client.searchRecords(request); + * for (const response of responses) { + * console.log(`Found: ${response.record?.name}`); + * } + * ``` + */ + async searchRecords( + request: models.search_v1.SearchRecordsRequest, + ): Promise { + const responses: models.search_v1.SearchRecordsResponse[] = []; + + for await (const response of this.searchClient.searchRecords(request)) { + responses.push(response); + } + + return responses; + } + + /** + * Look up metadata for records in the Store API. + * + * Retrieves metadata information for one or more records without + * downloading the full record content. This is useful for checking + * if records exist and getting basic information about them. + * + * @param refs - Array of RecordRef objects containing the CIDs to look up + * @returns Promise that resolves to an array of RecordMeta objects containing metadata for the records + * + * @throws {Error} If the gRPC call fails or the lookup operation fails + * + * @example + * ```typescript + * const refs = [new models.core_v1.RecordRef({cid: "QmExample123"})]; + * const metadatas = await client.lookup(refs); + * for (const meta of metadatas) { + * console.log(`Record size: ${meta.size}`); + * } + * ``` + */ + async lookup( + refs: models.core_v1.RecordRef[], + ): Promise { + const recordMetas: models.core_v1.RecordMeta[] = []; + + for await (const response of this.storeClient.lookup( + this.requestGenerator(refs), + )) { + recordMetas.push(response); + } + + return recordMetas; + } + + /** + * List objects from the Routing API matching the specified criteria. + * + * Returns a list of objects that match the filtering and + * query criteria specified in the request. + * + * @param request - ListRequest specifying filtering criteria, pagination, etc. + * @returns Promise that resolves to an array of ListResponse objects matching the criteria + * + * @throws {Error} If the gRPC call fails or the list operation fails + * + * @example + * ```typescript + * const request = new models.routing_v1.ListRequest({limit: 10}); + * const responses = await client.list(request); + * for (const response of responses) { + * console.log(`Found object: ${response.cid}`); + * } + * ``` + */ + async list( + request: models.routing_v1.ListRequest, + ): Promise { + const results: models.routing_v1.ListResponse[] = []; + + for await (const response of this.routingClient.list(request)) { + results.push(response); + } + + return results; + } + + /** + * Publish objects to the Routing API matching the specified criteria. + * + * Makes the specified objects available for discovery and retrieval by other + * clients in the network. The objects must already exist in the store before + * they can be published. + * + * @param request - PublishRequest containing the query for the objects to publish + * @returns Promise that resolves when the publish operation is complete + * + * @throws {Error} If the gRPC call fails or the object cannot be published + * + * @example + * ```typescript + * const ref = new models.routing_v1.RecordRef({cid: "QmExample123"}); + * const request = new models.routing_v1.PublishRequest({recordRefs: [ref]}); + * await client.publish(request); + * ``` + */ + async publish(request: models.routing_v1.PublishRequest): Promise { + await this.routingClient.publish(request); + } + + /** + * Unpublish objects from the Routing API matching the specified criteria. + * + * Removes the specified objects from the public network, making them no + * longer discoverable by other clients. The objects remain in the local + * store but are not available for network discovery. + * + * @param request - UnpublishRequest containing the query for the objects to unpublish + * @returns Promise that resolves when the unpublish operation is complete + * + * @throws {Error} If the gRPC call fails or the objects cannot be unpublished + * + * @example + * ```typescript + * const ref = new models.routing_v1.RecordRef({cid: "QmExample123"}); + * const request = new models.routing_v1.UnpublishRequest({recordRefs: [ref]}); + * await client.unpublish(request); + * ``` + */ + async unpublish(request: models.routing_v1.UnpublishRequest): Promise { + await this.routingClient.unpublish(request); + } + + /** + * Delete records from the Store API. + * + * Permanently removes one or more records from the content store using + * their content identifiers (CIDs). This operation cannot be undone. + * + * @param refs - Array of RecordRef objects containing the CIDs to delete + * @returns Promise that resolves when the deletion is complete + * + * @throws {Error} If the gRPC call fails or the delete operation fails + * + * @example + * ```typescript + * const refs = [new models.core_v1.RecordRef({cid: "QmExample123"})]; + * await client.delete(refs); + * ``` + */ + async delete(refs: models.core_v1.RecordRef[]): Promise { + await this.storeClient.delete(this.requestGenerator(refs)); + } + + /** + * Sign a record with a cryptographic signature. + * + * Creates a cryptographic signature for a record using either a private + * key or OIDC-based signing. The signing process uses the external dirctl + * command-line tool to perform the actual cryptographic operations. + * + * @param req - SignRequest containing the record reference and signing provider + * configuration. The provider can specify either key-based signing + * (with a private key) or OIDC-based signing + * @param oidc_client_id - OIDC client identifier for OIDC-based signing. Defaults to "sigstore" + * @returns SignResponse containing the signature + * + * @throws {Error} If the signing operation fails or unsupported provider is supplied + * + * @example + * ```typescript + * const req = new models.sign_v1.SignRequest({ + * recordRef: new models.core_v1.RecordRef({cid: "QmExample123"}), + * provider: new models.sign_v1.SignProvider({key: keyConfig}) + * }); + * const response = client.sign(req); + * console.log(`Signature: ${response.signature}`); + * ``` + */ + sign(req: models.sign_v1.SignRequest): void { + + var output; + + switch (req.provider?.request.case) { + case 'oidc': + output = this.__sign_with_oidc( + req.recordRef?.cid || '', + req.provider.request.value, + ); + break; + + case 'key': + output = this.__sign_with_key( + req.recordRef?.cid || '', + req.provider.request.value, + ); + break; + + default: + throw new Error('unsupported provider was supplied'); + } + + if (output.status !== 0) { + throw output.error || output.stderr; + } + } + + /** + * Verify a cryptographic signature on a record. + * + * Validates the cryptographic signature of a previously signed record + * to ensure its authenticity and integrity. This operation verifies + * that the record has not been tampered with since signing. + * + * The verification process uses the external dirctl command-line tool + * to perform the actual cryptographic operations. + * + * When fromServer is true, uses the server's cached verification result. + * + * @param request - VerifyRequest containing the record reference and verification parameters. + * The provider can specify either key-based verification (with a public key) + * or OIDC-based verification + * @returns VerifyResponse containing the verification result and details + * + * @throws {Error} If the verification operation fails or unsupported provider is supplied + * + * @example + * ```typescript + * const request = new models.sign_v1.VerifyRequest({ + * recordRef: new models.core_v1.RecordRef({cid: "QmExample123"}) + * }); + * const response = client.verify(request); + * console.log(`Signature valid: ${response.success}`); + * ``` + */ + async verify( + request: models.sign_v1.VerifyRequest, + ): Promise { + if (request.fromServer) { + return await this._verifyViaServer(request); + } + + // Create a temp file for output + const tempDir = mkdtempSync(join(tmpdir(), 'dirctl-verify-')); + const outputPath = join(tempDir, 'output.json'); + closeSync(openSync(outputPath, 'w')); // create empty file + let _outputPath = outputPath; + + if (this.config.dockerConfig) { + _outputPath = outputPath.split('/').reverse()[0]; + this.config.dockerConfig.mounts.push(`type=bind,src=${outputPath},dst=/${_outputPath}`); + } + + try { + switch (request.provider?.request.case) { + case 'oidc': + this.__verify_with_oidc( + request.recordRef?.cid || '', + request.provider.request.value, + _outputPath, + ); + break; + + case 'key': + this.__verify_with_key( + request.recordRef?.cid || '', + request.provider.request.value, + _outputPath, + ); + break; + + case 'any': + this.__verify_with_any( + request.recordRef?.cid || '', + request.provider.request.value, + _outputPath, + ); + break; + + default: + // Default: verify any valid signature + this.__verify_with_any( + request.recordRef?.cid || '', + undefined, + _outputPath, + ); + break; + } + + // Read and parse the output file + const jsonContent = readFileSync(outputPath, 'utf8'); + return fromJsonString(models.sign_v1.VerifyResponseSchema, jsonContent); + } catch (e) { + throw new Error(`Failed to parse verification response: ${e}`); + } finally { + // Clean up the temp directory + try { + rmSync(tempDir, { recursive: true, force: true }); + } catch { + // Ignore cleanup errors + } + } + } + + private async _verifyViaServer( + request: models.sign_v1.VerifyRequest, + ): Promise { + if (!request.recordRef?.cid) { + throw new Error('VerifyRequest.recordRef with cid is required'); + } + const response = await this.signClient.verify(request); + return response; + } + + /** + * Create a new synchronization configuration. + * + * Creates a new sync configuration that defines how data should be + * synchronized between different Directory servers. This allows for + * automated data replication and consistency across multiple locations. + * + * @param request - CreateSyncRequest containing the sync configuration details + * including source, target, and synchronization parameters + * @returns Promise that resolves to a CreateSyncResponse containing the created sync details + * including the sync ID and configuration + * + * @throws {Error} If the gRPC call fails or the sync creation fails + * + * @example + * ```typescript + * const request = new models.store_v1.CreateSyncRequest(); + * const response = await client.create_sync(request); + * console.log(`Created sync with ID: ${response.syncId}`); + * ``` + */ + async create_sync( + request: models.store_v1.CreateSyncRequest, + ): Promise { + return await this.syncClient.createSync(request); + } + + /** + * List existing synchronization configurations. + * + * Retrieves a list of all sync configurations that have been created, + * with optional filtering and pagination support. This allows you to + * monitor and manage multiple synchronization processes. + * + * @param request - ListSyncsRequest containing filtering criteria, pagination options, + * and other query parameters + * @returns Promise that resolves to an array of ListSyncsItem objects with + * their details including ID, name, status, and configuration parameters + * + * @throws {Error} If the gRPC call fails or the list operation fails + * + * @example + * ```typescript + * const request = new models.store_v1.ListSyncsRequest({limit: 10}); + * const syncs = await client.list_syncs(request); + * for (const sync of syncs) { + * console.log(`Sync: ${sync}`); + * } + * ``` + */ + async list_syncs( + request: models.store_v1.ListSyncsRequest, + ): Promise { + const results: models.store_v1.ListSyncsItem[] = []; + + for await (const response of this.syncClient.listSyncs(request)) { + results.push(response); + } + + return results; + } + + /** + * Retrieve detailed information about a specific synchronization configuration. + * + * Gets comprehensive details about a specific sync configuration including + * its current status, configuration parameters, performance metrics, + * and any recent errors or warnings. + * + * @param request - GetSyncRequest containing the sync ID or identifier to retrieve + * @returns Promise that resolves to a GetSyncResponse with detailed information about the sync configuration + * including status, metrics, configuration, and logs + * + * @throws {Error} If the gRPC call fails or the get operation fails + * + * @example + * ```typescript + * const request = new models.store_v1.GetSyncRequest({syncId: "sync-123"}); + * const response = await client.get_sync(request); + * console.log(`Sync status: ${response.status}`); + * console.log(`Last update: ${response.lastUpdateTime}`); + * ``` + */ + async get_sync( + request: models.store_v1.GetSyncRequest, + ): Promise { + return await this.syncClient.getSync(request); + } + + /** + * Delete a synchronization configuration. + * + * Permanently removes a sync configuration and stops any ongoing + * synchronization processes. This operation cannot be undone and + * will halt all data synchronization for the specified configuration. + * + * @param request - DeleteSyncRequest containing the sync ID or identifier to delete + * @returns Promise that resolves to a DeleteSyncResponse when the deletion is complete + * + * @throws {Error} If the gRPC call fails or the delete operation fails + * + * @example + * ```typescript + * const request = new models.store_v1.DeleteSyncRequest({syncId: "sync-123"}); + * await client.delete_sync(request); + * console.log("Sync deleted"); + * ``` + */ + async delete_sync( + request: models.store_v1.DeleteSyncRequest, + ): Promise { + return await this.syncClient.deleteSync(request); + } + + /** + * Get events from the Event API matching the specified criteria. + * + * Retrieves a list of events that match the filtering and query criteria + * specified in the request. + * + * @param request - ListenRequest specifying filtering criteria, pagination, etc. + * @returns Promise that resolves to an array of ListenResponse objects matching the criteria + * + * @throws {Error} If the gRPC call fails or the get events operation fails + */ + listen( + request: models.events_v1.ListenRequest + ): AsyncIterable { + return this.eventClient.listen(request); + } + + /** + * CreatePublication creates a new publication request that will be processed by the PublicationWorker. + * The publication request can specify either a query, a list of specific CIDs, + * or all records to be announced to the DHT. + * + * @param request - PublishRequest containing record references and queries options. + * + * @returns CreatePublicationResponse returns the result of creating a publication request. + * This includes the publication ID and any relevant metadata. + * + * @throws {Error} If the gRPC call fails or the list operation fails + */ + async create_publication( + request: models.routing_v1.PublishRequest, + ): Promise { + return await this.publicationClient.createPublication(request); + } + + /** + * ListPublications returns a stream of all publication requests in the system. + * This allows monitoring of pending, processing, and completed publication requests. + * + * @param request - ListPublicationsRequest contains optional filters for listing publication requests. + * + * @returns Promise that resolves to an array of ListPublicationsItem represents + * a single publication request in the list response. + * Contains publication details including ID, status, and creation timestamp. + * + * @throws {Error} If the gRPC call fails or the list operation fails + */ + async list_publication( + request: models.routing_v1.ListPublicationsRequest, + ): Promise { + const results: models.routing_v1.ListPublicationsItem[] = []; + + for await (const response of this.publicationClient.listPublications(request)) { + results.push(response); + } + + return results; + } + + /** + * GetPublication retrieves details of a specific publication request by its identifier. + * This includes the current status and any associated metadata. + * + * @param request - GetPublicationRequest specifies which publication to retrieve by its identifier. + * + * @returns GetPublicationResponse contains the full details of a specific publication request. + * Includes status, progress information, and any error details if applicable. + * + * @throws {Error} If the gRPC call fails or the get operation fails + */ + async get_publication( + request: models.routing_v1.GetPublicationRequest, + ): Promise { + return await this.publicationClient.getPublication(request); + } + + /** + * Resolve a record name to CIDs. + * + * Resolves a record reference (name with optional version) to content identifiers (CIDs). + * When no version is specified, returns all versions sorted by creation time (newest first). + * + * @param request - ResolveRequest containing the name and optional version + * @returns Promise that resolves to a ResolveResponse containing the resolved record references + * + * @throws {Error} If the gRPC call fails or the resolve operation fails + * + * @example + * ```typescript + * import { create } from "@bufbuild/protobuf"; + * + * // Resolve latest version + * const request = create(models.naming_v1.ResolveRequestSchema, { name: "cisco.com/agent" }); + * const response = await client.resolve(request); + * console.log(`Latest CID: ${response.records[0].cid}`); + * + * // Resolve specific version + * const request = create(models.naming_v1.ResolveRequestSchema, { name: "cisco.com/agent", version: "v1.0.0" }); + * const response = await client.resolve(request); + * ``` + */ + async resolve( + request: models.naming_v1.ResolveRequest, + ): Promise { + return await this.namingClient.resolve(request); + } + + /** + * Get verification info for a record. + * + * Retrieves the name verification status for a record. Can look up by CID directly + * or by name (with optional version) which will be resolved first. + * + * @param request - GetVerificationInfoRequest containing cid, name, and/or version + * @returns Promise that resolves to a GetVerificationInfoResponse containing verification status + * + * @throws {Error} If the gRPC call fails or the operation fails + * + * @example + * ```typescript + * import { create } from "@bufbuild/protobuf"; + * + * // Check by CID + * const request = create(models.naming_v1.GetVerificationInfoRequestSchema, { cid: "bafyreib..." }); + * const response = await client.getVerificationInfo(request); + * + * // Check by name (latest version) + * const request = create(models.naming_v1.GetVerificationInfoRequestSchema, { name: "cisco.com/agent" }); + * const response = await client.getVerificationInfo(request); + * + * // Check by name with specific version + * const request = create(models.naming_v1.GetVerificationInfoRequestSchema, { name: "cisco.com/agent", version: "v1.0.0" }); + * const response = await client.getVerificationInfo(request); + * ``` + */ + async getVerificationInfo( + request: models.naming_v1.GetVerificationInfoRequest, + ): Promise { + return await this.namingClient.getVerificationInfo(request); + } + + /** + * Sign a record using a private key. + * + * This private method handles key-based signing by passing the key reference + * directly to the dirctl command. The key can be a file path, URL, or KMS URI. + * + * @param cid - Content identifier of the record to sign + * @param req - SignWithKey request containing the private key reference + * @returns SignResponse containing the signature + * + * @throws {Error} If any error occurs during signing + * + * @private + */ + private __sign_with_key(cid: string, req: models.sign_v1.SignWithKey): SpawnSyncReturns { + // Prepare environment for command + // Always set COSIGN_PASSWORD (even if empty) to avoid terminal prompts + const shell_env = { ...env }; + shell_env['COSIGN_PASSWORD'] = req.password ? String(req.password) : ''; + if (this.config.dockerConfig) { + this.config.dockerConfig.envs.set("COSIGN_PASSWORD", shell_env["COSIGN_PASSWORD"]); + } + + // Pass key reference directly to dirctl which handles + // file paths, URLs, KMS URIs, etc. + const args = ["sign", cid, "--key", req.privateKey]; + const [command, commandArgs] = this.config.getCommandAndArgs(args); + + // Execute command + const output = spawnSync( + command, commandArgs, + { env: shell_env, encoding: 'utf8', stdio: 'pipe' }, + ); + + return output; + } + + /** + * Sign a record using OIDC-based authentication. + * + * This private method handles OIDC-based signing by building the appropriate + * dirctl command with OIDC parameters and executing it. + * + * @param cid - Content identifier of the record to sign + * @param req - SignWithOIDC request containing the OIDC configuration + * @returns SignResponse containing the signature + * + * @throws {Error} If any error occurs during signing + * + * @private + */ + private __sign_with_oidc( + cid: string, + req: models.sign_v1.SignWithOIDC, + ): SpawnSyncReturns { + // Prepare command + let args = ["sign", cid]; + if (req.idToken !== '') { + args.push(...["--oidc-token", req.idToken]); + } + if ( + req.options?.oidcProviderUrl !== undefined && + req.options.oidcProviderUrl !== '' + ) { + args.push(...["--oidc-provider-url", req.options.oidcProviderUrl]); + } + if (req.options?.oidcClientId !== undefined && req.options.oidcClientId !== '') { + args.push(...["--oidc-client-id", req.options.oidcClientId]); + } + if (req.options?.oidcClientSecret !== undefined && req.options.oidcClientSecret !== '') { + args.push(...["--oidc-client-secret", req.options.oidcClientSecret]); + } + if (req.options?.skipTlog !== undefined && req.options.skipTlog) { + args.push("--skip-tlog"); + } + if (req.options?.fulcioUrl !== undefined && req.options.fulcioUrl !== '') { + args.push(...["--fulcio-url", req.options.fulcioUrl]); + } + if (req.options?.rekorUrl !== undefined && req.options.rekorUrl !== '') { + args.push(...["--rekor-url", req.options.rekorUrl]); + } + if ( + req.options?.timestampUrl !== undefined && + req.options.timestampUrl !== '' + ) { + args.push(...["--timestamp-url", req.options.timestampUrl]); + } + + const [command, commandArgs] = this.config.getCommandAndArgs(args); + + // Execute command + let output = spawnSync(command, commandArgs, { + env: { ...env }, + encoding: 'utf8', + stdio: 'pipe', + }); + + return output; + } + + /** + * Verify a record using a public key. + * + * This private method handles key-based verification by passing the public key + * reference to the dirctl command. The key can be a file path, URL, or KMS URI. + * + * @param cid - Content identifier of the record to verify + * @param req - VerifyWithKey request containing the public key reference + * @param outputPath - Path to the output file for the verification result + * + * @throws {Error} If any error occurs during verification + * + * @private + */ + private __verify_with_key(cid: string, req: models.sign_v1.VerifyWithKey, outputPath: string): void { + // Pass key reference directly to dirctl which handles + // file paths, URLs, KMS URIs, etc. + const args = ["verify", cid, "--key", req.publicKey, "--output-file", outputPath]; + const [command, commandArgs] = this.config.getCommandAndArgs(args); + + // Execute command + let output = spawnSync( + command, commandArgs, + { env: { ...env }, encoding: 'utf8', stdio: 'pipe' }, + ); + + if (output.status !== 0) { + throw new Error(output.stderr || output.stdout || 'Verification failed'); + } + } + + /** + * Verify a record with any valid signature. + * + * This private method handles verification that accepts any valid signature, + * with optional OIDC verification options for additional constraints. + * + * @param cid - Content identifier of the record to verify + * @param req - VerifyWithAny request containing optional OIDC options, or undefined for default verification + * @param outputPath - Path to the output file for the verification result + * + * @throws {Error} If any error occurs during verification + * + * @private + */ + private __verify_with_any( + cid: string, + req: models.sign_v1.VerifyWithAny | undefined, + outputPath: string, + ): void { + // Prepare command + let args = ["verify", cid, "--output-file", outputPath]; + + // Add OIDC options if provided + if (req?.oidcOptions !== undefined) { + if (req.oidcOptions.tufMirrorUrl !== undefined && req.oidcOptions.tufMirrorUrl !== '') { + args.push(...["--tuf-mirror-url", req.oidcOptions.tufMirrorUrl]); + } + if (req.oidcOptions.trustedRootPath !== undefined && req.oidcOptions.trustedRootPath !== '') { + args.push(...["--trusted-root-path", req.oidcOptions.trustedRootPath]); + } + if (req.oidcOptions.ignoreTlog === true) { + args.push("--ignore-tlog"); + } + if (req.oidcOptions.ignoreTsa === true) { + args.push("--ignore-tsa"); + } + if (req.oidcOptions.ignoreSct === true) { + args.push("--ignore-sct"); + } + } + + const [command, commandArgs] = this.config.getCommandAndArgs(args); + + // Execute command + let output = spawnSync(command, commandArgs, { + env: { ...env }, + encoding: 'utf8', + stdio: 'pipe', + }); + + if (output.status !== 0) { + throw new Error(output.stderr || output.stdout || 'Verification failed'); + } + } + + /** + * Verify a record using OIDC-based verification. + * + * This private method handles OIDC-based verification by building the appropriate + * dirctl command with OIDC parameters and executing it. + * + * @param cid - Content identifier of the record to verify + * @param req - VerifyWithOIDC request containing the OIDC configuration, or undefined for default verification + * @param outputPath - Path to the output file for the verification result + * + * @throws {Error} If any error occurs during verification + * + * @private + */ + private __verify_with_oidc( + cid: string, + req: models.sign_v1.VerifyWithOIDC | undefined, + outputPath: string, + ): void { + // Prepare command + let args = ["verify", cid, "--output-file", outputPath]; + + // Add OIDC-specific parameters if provided + if (req !== undefined) { + if (req.issuer !== undefined && req.issuer !== '') { + args.push(...["--oidc-issuer", req.issuer]); + } + if (req.subject !== undefined && req.subject !== '') { + args.push(...["--oidc-subject", req.subject]); + } + + // Add verification options if present + if (req.options !== undefined) { + if (req.options.tufMirrorUrl !== undefined && req.options.tufMirrorUrl !== '') { + args.push(...["--tuf-mirror-url", req.options.tufMirrorUrl]); + } + if (req.options.trustedRootPath !== undefined && req.options.trustedRootPath !== '') { + args.push(...["--trusted-root-path", req.options.trustedRootPath]); + } + if (req.options.ignoreTlog === true) { + args.push("--ignore-tlog"); + } + if (req.options.ignoreTsa === true) { + args.push("--ignore-tsa"); + } + if (req.options.ignoreSct === true) { + args.push("--ignore-sct"); + } + } + } + + const [command, commandArgs] = this.config.getCommandAndArgs(args); + + // Execute command + let output = spawnSync(command, commandArgs, { + env: { ...env }, + encoding: 'utf8', + stdio: 'pipe', + }); + + if (output.status !== 0) { + throw new Error(output.stderr || output.stdout || 'Verification failed'); + } + } +} diff --git a/src/client/index.ts b/src/client/index.ts new file mode 100644 index 0000000..e85cba1 --- /dev/null +++ b/src/client/index.ts @@ -0,0 +1,4 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +export * from './client'; diff --git a/src/client/oauthPkce.ts b/src/client/oauthPkce.ts new file mode 100644 index 0000000..422675b --- /dev/null +++ b/src/client/oauthPkce.ts @@ -0,0 +1,408 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +import { createHash, randomBytes } from 'node:crypto'; +import { spawn } from 'node:child_process'; +import * as http from 'node:http'; +import * as https from 'node:https'; +import { platform } from 'node:process'; +import { URL } from 'node:url'; + +export class OAuthPkceError extends Error { + constructor(message: string) { + super(message); + this.name = 'OAuthPkceError'; + } +} + +/** Subset of client config used by PKCE helpers (avoids circular imports). */ +export interface OidcPkceConfig { + oidcIssuer: string; + oidcClientId: string; + oidcClientSecret: string; + oidcRedirectUri: string; + oidcCallbackPort: number; + oidcAuthTimeout: number; + oidcScopes: string[]; +} + +export function normalizeIssuer(issuer: string): string { + const u = issuer.replace(/\/+$/, ''); + if (!u.startsWith('https://') && !u.startsWith('http://')) { + throw new Error('oidc_issuer must be an absolute URL (https:// recommended)'); + } + return u; +} + +export interface OpenIdConfiguration { + authorization_endpoint: string; + token_endpoint: string; + [key: string]: unknown; +} + +async function httpRequest( + method: string, + urlStr: string, + body: string | undefined, + headers: Record, + options: { timeoutMs: number; rejectUnauthorized: boolean }, +): Promise<{ status: number; text: string }> { + return new Promise((resolve, reject) => { + const u = new URL(urlStr); + const isHttps = u.protocol === 'https:'; + const lib = isHttps ? https : http; + const port = + u.port !== '' + ? Number(u.port) + : isHttps + ? 443 + : 80; + const req = lib.request( + { + hostname: u.hostname, + port, + path: `${u.pathname}${u.search}`, + method, + headers, + ...(isHttps ? { rejectUnauthorized: options.rejectUnauthorized } : {}), + }, + (res) => { + const chunks: Buffer[] = []; + res.on('data', (c: Buffer) => chunks.push(c)); + res.on('end', () => { + resolve({ + status: res.statusCode ?? 0, + text: Buffer.concat(chunks).toString('utf-8'), + }); + }); + }, + ); + req.setTimeout(options.timeoutMs, () => { + req.destroy(); + reject(new Error('Request timeout')); + }); + req.on('error', reject); + if (body !== undefined) { + req.write(body); + } + req.end(); + }); +} + +export async function fetchOpenidConfiguration( + issuer: string, + options: { verify: boolean; timeoutMs: number }, +): Promise { + const base = normalizeIssuer(issuer); + const url = `${base}/.well-known/openid-configuration`; + let response: { status: number; text: string }; + try { + response = await httpRequest('GET', url, undefined, {}, { + timeoutMs: options.timeoutMs, + rejectUnauthorized: options.verify, + }); + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + throw new OAuthPkceError(`OpenID discovery failed: ${msg}`); + } + if (response.status < 200 || response.status >= 300) { + throw new OAuthPkceError(`OpenID discovery HTTP ${response.status}: ${response.text.slice(0, 500)}`); + } + let data: Record; + try { + data = JSON.parse(response.text) as Record; + } catch { + throw new OAuthPkceError('OpenID discovery response is not valid JSON'); + } + if (typeof data.authorization_endpoint !== 'string' || typeof data.token_endpoint !== 'string') { + throw new OAuthPkceError( + 'OpenID configuration missing authorization_endpoint or token_endpoint', + ); + } + return data as OpenIdConfiguration; +} + +function urlEncodeForm(body: Record): string { + return new URLSearchParams(body).toString(); +} + +export async function exchangeAuthorizationCode( + tokenEndpoint: string, + params: { + code: string; + redirectUri: string; + clientId: string; + codeVerifier: string; + clientSecret: string; + verify: boolean; + timeoutMs: number; + }, +): Promise> { + const body: Record = { + grant_type: 'authorization_code', + code: params.code, + redirect_uri: params.redirectUri, + client_id: params.clientId, + code_verifier: params.codeVerifier, + }; + if (params.clientSecret) { + body.client_secret = params.clientSecret; + } + const encoded = urlEncodeForm(body); + const response = await httpRequest( + 'POST', + tokenEndpoint, + encoded, + { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': String(Buffer.byteLength(encoded)) }, + { timeoutMs: params.timeoutMs, rejectUnauthorized: params.verify }, + ); + let json: Record; + try { + json = JSON.parse(response.text) as Record; + } catch { + throw new OAuthPkceError(`Token response not JSON: ${response.text.slice(0, 500)}`); + } + if (response.status < 200 || response.status >= 300) { + throw new OAuthPkceError(`Token HTTP ${response.status}: ${response.text.slice(0, 500)}`); + } + return json; +} + +function base64Url(buf: Buffer): string { + return buf + .toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); +} + +function createS256CodeChallenge(verifier: string): string { + const hash = createHash('sha256').update(verifier, 'utf8').digest(); + return base64Url(hash); +} + +function openBrowser(url: string): void { + const os = platform; + if (os === 'darwin') { + spawn('open', [url], { stdio: 'ignore', detached: true }).unref(); + } else if (os === 'win32') { + spawn('cmd', ['/c', 'start', '""', url], { stdio: 'ignore', detached: true, shell: false }).unref(); + } else { + spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref(); + } +} + +export class OAuthTokenHolder { + private accessToken: string | undefined; + + setTokens(accessToken: string): void { + this.accessToken = accessToken; + } + + updateFromTokenResponse(payload: Record): void { + const access = payload.access_token; + if (!access || typeof access !== 'string') { + throw new OAuthPkceError('Token response missing access_token'); + } + this.setTokens(access); + } + + getAccessToken(): string { + if (this.accessToken === undefined) { + throw new Error( + 'No OAuth access token: set DIRECTORY_CLIENT_AUTH_TOKEN or call Client.authenticateOAuthPkce()', + ); + } + return this.accessToken; + } +} + +export async function runLoopbackPkceLogin( + config: OidcPkceConfig, + metadata: OpenIdConfiguration | undefined, + options: { verify: boolean; timeoutMs: number }, +): Promise> { + if (!config.oidcIssuer) { + throw new Error('oidc_issuer is required for OAuth PKCE'); + } + if (!config.oidcClientId) { + throw new Error('oidc_client_id is required for OAuth PKCE'); + } + + const redirectUri = config.oidcRedirectUri.trim(); + const parsed = new URL(redirectUri); + if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { + throw new Error('oidc_redirect_uri must be an absolute http(s) URL'); + } + if (parsed.hostname !== 'localhost' && parsed.hostname !== '127.0.0.1') { + throw new Error('loopback PKCE requires redirect host localhost or 127.0.0.1'); + } + let path = parsed.pathname || '/'; + if (!path.startsWith('/')) { + path = `/${path}`; + } + + const port = config.oidcCallbackPort; + + const meta = + metadata ?? + (await fetchOpenidConfiguration(config.oidcIssuer, { + verify: options.verify, + timeoutMs: Math.min(30_000, options.timeoutMs), + })); + + const authEp = meta.authorization_endpoint; + const tokenEp = meta.token_endpoint; + + const codeVerifier = base64Url(randomBytes(48)); + const codeChallenge = createS256CodeChallenge(codeVerifier); + const state = base64Url(randomBytes(24)); + + const result: { code?: string } = {}; + const errorHolder: string[] = []; + + let settled = false; + let resolveDone!: () => void; + const donePromise = new Promise((r) => { + resolveDone = r; + }); + function settle(): void { + if (!settled) { + settled = true; + resolveDone(); + } + } + + const server = http.createServer((req, res) => { + try { + if (!req.url) { + errorHolder.push('missing url'); + res.writeHead(400); + res.end(); + return; + } + const u = new URL(req.url, `http://127.0.0.1:${port}`); + if (u.pathname !== path) { + errorHolder.push('redirect path does not match oidc_redirect_uri'); + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Not Found'); + return; + } + const err = u.searchParams.get('error'); + if (err) { + const desc = u.searchParams.get('error_description') ?? ''; + errorHolder.push(`${err}: ${desc}`); + const body = Buffer.from( + '

Authorization failed. You may close this window.

', + 'utf-8', + ); + res.writeHead(200, { + 'Content-Type': 'text/html; charset=utf-8', + 'Content-Length': String(body.length), + 'Cache-Control': 'no-store', + }); + res.end(body); + return; + } + if (u.searchParams.get('state') !== state) { + errorHolder.push('state mismatch'); + const body = Buffer.from( + '

Invalid state. You may close this window.

', + 'utf-8', + ); + res.writeHead(200, { + 'Content-Type': 'text/html; charset=utf-8', + 'Content-Length': String(body.length), + 'Cache-Control': 'no-store', + }); + res.end(body); + return; + } + const code = u.searchParams.get('code'); + if (!code) { + errorHolder.push('missing code'); + const body = Buffer.from( + '

Missing code. You may close this window.

', + 'utf-8', + ); + res.writeHead(200, { + 'Content-Type': 'text/html; charset=utf-8', + 'Content-Length': String(body.length), + 'Cache-Control': 'no-store', + }); + res.end(body); + return; + } + result.code = code; + const body = Buffer.from( + '

Login successful. You may close this window.

', + 'utf-8', + ); + res.writeHead(200, { + 'Content-Type': 'text/html; charset=utf-8', + 'Content-Length': String(body.length), + 'Cache-Control': 'no-store', + }); + res.end(body); + } finally { + settle(); + } + }); + + await new Promise((resolve, reject) => { + server.once('error', reject); + server.listen(port, '127.0.0.1', () => resolve()); + }); + + const scopeStr = + config.oidcScopes && config.oidcScopes.length > 0 + ? config.oidcScopes.join(' ') + : 'openid'; + const authParams = new URLSearchParams({ + response_type: 'code', + client_id: config.oidcClientId, + redirect_uri: redirectUri, + scope: scopeStr, + state, + code_challenge: codeChallenge, + code_challenge_method: 'S256', + }); + const sep = authEp.includes('?') ? '&' : '?'; + const authorizeUrl = `${authEp}${sep}${authParams.toString()}`; + + openBrowser(authorizeUrl); + + const waitMs = config.oidcAuthTimeout * 1000; + try { + await Promise.race([ + donePromise, + new Promise((_, rej) => { + setTimeout(() => { + rej(new OAuthPkceError(`OAuth callback timed out after ${config.oidcAuthTimeout}s`)); + }, waitMs); + }), + ]); + } finally { + await new Promise((r) => { + server.close(() => r()); + }); + } + + if (errorHolder.length > 0) { + throw new OAuthPkceError(errorHolder[0] ?? 'unknown error'); + } + const code = result.code; + if (!code) { + throw new OAuthPkceError('Authorization did not return a code'); + } + + return exchangeAuthorizationCode(tokenEp, { + code, + redirectUri: redirectUri, + clientId: config.oidcClientId, + codeVerifier, + clientSecret: config.oidcClientSecret, + verify: options.verify, + timeoutMs: Math.min(30_000, options.timeoutMs), + }); +} diff --git a/src/client/tokenCache.ts b/src/client/tokenCache.ts new file mode 100644 index 0000000..2fa247f --- /dev/null +++ b/src/client/tokenCache.ts @@ -0,0 +1,208 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { join } from 'node:path'; +import { env } from 'node:process'; + +export const DEFAULT_TOKEN_CACHE_DIR = 'dirctl'; +export const TOKEN_CACHE_FILE = 'auth-token.json'; + +const DEFAULT_TOKEN_VALIDITY_MS = 8 * 60 * 60 * 1000; +const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000; +const CACHE_DIR_PERMS = 0o700; +const CACHE_FILE_PERMS = 0o600; + +function utcNow(): Date { + return new Date(); +} + +function parseTimestamp(value: string | undefined | null): Date | undefined { + if (!value) { + return undefined; + } + const normalized = value.replace('Z', '+00:00'); + const d = new Date(normalized); + return Number.isNaN(d.getTime()) ? undefined : d; +} + +function formatTimestamp(value: Date | undefined): string | undefined { + if (value === undefined) { + return undefined; + } + return value.toISOString(); +} + +export interface CachedTokenJson { + access_token: string; + token_type?: string; + provider?: string; + issuer?: string; + refresh_token?: string; + expires_at?: string; + user?: string; + user_id?: string; + email?: string; + created_at?: string; +} + +export class CachedToken { + accessToken: string; + tokenType: string; + provider: string; + issuer: string; + refreshToken: string; + expiresAt: Date | undefined; + user: string; + userId: string; + email: string; + createdAt: Date | undefined; + + constructor( + accessToken: string, + tokenType = '', + provider = '', + issuer = '', + refreshToken = '', + expiresAt: Date | undefined = undefined, + user = '', + userId = '', + email = '', + createdAt: Date | undefined = undefined, + ) { + this.accessToken = accessToken; + this.tokenType = tokenType; + this.provider = provider; + this.issuer = issuer; + this.refreshToken = refreshToken; + this.expiresAt = expiresAt; + this.user = user; + this.userId = userId; + this.email = email; + this.createdAt = createdAt; + } + + static fromJson(payload: CachedTokenJson): CachedToken { + return new CachedToken( + String(payload.access_token ?? ''), + String(payload.token_type ?? ''), + String(payload.provider ?? ''), + String(payload.issuer ?? ''), + String(payload.refresh_token ?? ''), + parseTimestamp(payload.expires_at), + String(payload.user ?? ''), + String(payload.user_id ?? ''), + String(payload.email ?? ''), + parseTimestamp(payload.created_at), + ); + } + + toJson(): Record { + const payload: Record = { + access_token: this.accessToken, + created_at: formatTimestamp(this.createdAt ?? utcNow()) ?? '', + }; + if (this.tokenType) { + payload.token_type = this.tokenType; + } + if (this.provider) { + payload.provider = this.provider; + } + if (this.issuer) { + payload.issuer = this.issuer; + } + if (this.refreshToken) { + payload.refresh_token = this.refreshToken; + } + const exp = formatTimestamp(this.expiresAt); + if (exp !== undefined) { + payload.expires_at = exp; + } + if (this.user) { + payload.user = this.user; + } + if (this.userId) { + payload.user_id = this.userId; + } + if (this.email) { + payload.email = this.email; + } + return payload; + } +} + +export class TokenCache { + readonly cacheDir: string; + + constructor(cacheDir?: string) { + if (cacheDir !== undefined) { + this.cacheDir = cacheDir; + } else { + const configHome = env['XDG_CONFIG_HOME']; + const baseDir = configHome ? configHome : join(homedir(), '.config'); + this.cacheDir = join(baseDir, DEFAULT_TOKEN_CACHE_DIR); + } + } + + getCachePath(): string { + return join(this.cacheDir, TOKEN_CACHE_FILE); + } + + load(): CachedToken | undefined { + const path = this.getCachePath(); + if (!existsSync(path)) { + return undefined; + } + const raw = readFileSync(path, 'utf-8'); + const payload = JSON.parse(raw) as CachedTokenJson; + return CachedToken.fromJson(payload); + } + + save(token: CachedToken): void { + mkdirSync(this.cacheDir, { mode: CACHE_DIR_PERMS, recursive: true }); + try { + chmodSync(this.cacheDir, CACHE_DIR_PERMS); + } catch { + // ignore chmod failures (e.g. Windows) + } + if (token.createdAt === undefined) { + token.createdAt = utcNow(); + } + const serialized = `${JSON.stringify(token.toJson(), null, 2)}\n`; + const path = this.getCachePath(); + writeFileSync(path, serialized, { encoding: 'utf-8', mode: CACHE_FILE_PERMS }); + try { + chmodSync(path, CACHE_FILE_PERMS); + } catch { + // ignore chmod failures + } + } + + clear(): void { + const path = this.getCachePath(); + if (existsSync(path)) { + unlinkSync(path); + } + } + + isValid(token: CachedToken | undefined): boolean { + if (token === undefined || !token.accessToken) { + return false; + } + const now = utcNow().getTime(); + if (token.expiresAt === undefined) { + const createdAt = token.createdAt ?? new Date(now); + return now < createdAt.getTime() + DEFAULT_TOKEN_VALIDITY_MS; + } + return now + TOKEN_EXPIRY_BUFFER_MS < token.expiresAt.getTime(); + } + + getValidToken(): CachedToken | undefined { + const token = this.load(); + if (!this.isValid(token)) { + return undefined; + } + return token; + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..41226f9 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,5 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +export * from './client'; +export * as models from './models'; diff --git a/src/models/README.md b/src/models/README.md new file mode 100644 index 0000000..fcf29be --- /dev/null +++ b/src/models/README.md @@ -0,0 +1,12 @@ +# Directory SDK Models + +Directory models are distributed via `buf.build` and generated from Protocol Buffers definitions, +which can become cumbersome to import and use. +This module simplifies the imports and usage of data models needed by Directory APIs. +It re-exports all the models from the generated code into dedicated namespaces so that they can be imported directly from this module. + +For example, instead of importing `RecordMeta` from the generated code, use: + +```js +import { RecordMeta } from 'agntcy-dir/models/core_v1'; +``` diff --git a/src/models/core_v1.ts b/src/models/core_v1.ts new file mode 100644 index 0000000..480bf4d --- /dev/null +++ b/src/models/core_v1.ts @@ -0,0 +1,16 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +export { + file_agntcy_dir_core_v1_record, + RecordRef, + RecordRefSchema, + NamedRecordRef, + NamedRecordRefSchema, + RecordMeta, + RecordMetaSchema, + Record, + RecordSchema, + RecordReferrer, + RecordReferrerSchema, +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/core/v1/record_pb'; diff --git a/src/models/events_v1.ts b/src/models/events_v1.ts new file mode 100644 index 0000000..8d3d3b2 --- /dev/null +++ b/src/models/events_v1.ts @@ -0,0 +1,15 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +export { + file_agntcy_dir_events_v1_event_service, + ListenRequest, + ListenRequestSchema, + ListenResponse, + ListenResponseSchema, + Event, + EventSchema, + EventType, + EventTypeSchema, + EventService, +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/events/v1/event_service_pb'; diff --git a/src/models/index.ts b/src/models/index.ts new file mode 100644 index 0000000..21dc7d3 --- /dev/null +++ b/src/models/index.ts @@ -0,0 +1,10 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +export * as core_v1 from './core_v1'; +export * as naming_v1 from './naming_v1'; +export * as routing_v1 from './routing_v1'; +export * as search_v1 from './search_v1'; +export * as sign_v1 from './sign_v1'; +export * as store_v1 from './store_v1'; +export * as events_v1 from './events_v1'; diff --git a/src/models/naming_v1.ts b/src/models/naming_v1.ts new file mode 100644 index 0000000..9e0135b --- /dev/null +++ b/src/models/naming_v1.ts @@ -0,0 +1,22 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +export { + file_agntcy_dir_naming_v1_naming_service, + GetVerificationInfoRequest, + GetVerificationInfoRequestSchema, + GetVerificationInfoResponse, + GetVerificationInfoResponseSchema, + ResolveRequest, + ResolveRequestSchema, + ResolveResponse, + ResolveResponseSchema, + NamingService, +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/naming/v1/naming_service_pb'; +export { + file_agntcy_dir_naming_v1_name_verification, + Verification, + VerificationSchema, + DomainVerification, + DomainVerificationSchema +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/naming/v1/name_verification_pb'; diff --git a/src/models/routing_v1.ts b/src/models/routing_v1.ts new file mode 100644 index 0000000..a957719 --- /dev/null +++ b/src/models/routing_v1.ts @@ -0,0 +1,53 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +export { + file_agntcy_dir_routing_v1_peer, + Peer, + PeerSchema, + PeerConnectionType, + PeerConnectionTypeSchema, +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/routing/v1/peer_pb'; +export { + file_agntcy_dir_routing_v1_publication_service, + CreatePublicationResponse, + CreatePublicationResponseSchema, + ListPublicationsRequest, + ListPublicationsRequestSchema, + ListPublicationsItem, + ListPublicationsItemSchema, + GetPublicationRequest, + GetPublicationRequestSchema, + GetPublicationResponse, + GetPublicationResponseSchema, + PublicationStatus, + PublicationStatusSchema, + PublicationService +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/routing/v1/publication_service_pb'; +export { + file_agntcy_dir_routing_v1_record_query, + RecordQuery, + RecordQuerySchema, + RecordQueryType, + RecordQueryTypeSchema, +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/routing/v1/record_query_pb'; +export { + file_agntcy_dir_routing_v1_routing_service, + PublishRequest, + PublishRequestSchema, + UnpublishRequest, + UnpublishRequestSchema, + RecordRefs, + RecordRefsSchema, + RecordQueries, + RecordQueriesSchema, + SearchRequest, + SearchRequestSchema, + SearchResponse, + SearchResponseSchema, + ListRequest, + ListRequestSchema, + ListResponse, + ListResponseSchema, + RoutingService, +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/routing/v1/routing_service_pb'; diff --git a/src/models/search_v1.ts b/src/models/search_v1.ts new file mode 100644 index 0000000..eadfe58 --- /dev/null +++ b/src/models/search_v1.ts @@ -0,0 +1,22 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +export { + file_agntcy_dir_search_v1_record_query, + RecordQuery, + RecordQuerySchema, + RecordQueryType, + RecordQueryTypeSchema, +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/search/v1/record_query_pb'; +export { + file_agntcy_dir_search_v1_search_service, + SearchCIDsRequest, + SearchCIDsRequestSchema, + SearchRecordsRequest, + SearchRecordsRequestSchema, + SearchCIDsResponse, + SearchCIDsResponseSchema, + SearchRecordsResponse, + SearchRecordsResponseSchema, + SearchService, +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/search/v1/search_service_pb'; diff --git a/src/models/sign_v1.ts b/src/models/sign_v1.ts new file mode 100644 index 0000000..1e12885 --- /dev/null +++ b/src/models/sign_v1.ts @@ -0,0 +1,49 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +export { + file_agntcy_dir_sign_v1_sign_service, + SignOptionsOIDC, + SignOptionsOIDCSchema, + VerifyOptionsOIDC, + VerifyOptionsOIDCSchema, + SignRequest, + SignRequestSchema, + SignRequestProvider, + SignRequestProviderSchema, + SignWithKey, + SignWithKeySchema, + SignWithOIDC, + SignWithOIDCSchema, + SignResponse, + SignResponseSchema, + VerifyRequest, + VerifyRequestSchema, + VerifyRequestProvider, + VerifyRequestProviderSchema, + VerifyWithKey, + VerifyWithKeySchema, + VerifyWithOIDC, + VerifyWithOIDCSchema, + VerifyWithAny, + VerifyWithAnySchema, + VerifyResponse, + VerifyResponseSchema, + SignerInfo, + SignerInfoSchema, + SignerInfoKey, + SignerInfoKeySchema, + SignerInfoOIDC, + SignerInfoOIDCSchema, + SignService, +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/sign/v1/sign_service_pb'; +export { + file_agntcy_dir_sign_v1_signature, + Signature, + SignatureSchema, +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/sign/v1/signature_pb'; +export { + file_agntcy_dir_sign_v1_public_key, + PublicKey, + PublicKeySchema, +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/sign/v1/public_key_pb'; diff --git a/src/models/store_v1.ts b/src/models/store_v1.ts new file mode 100644 index 0000000..0e72f3a --- /dev/null +++ b/src/models/store_v1.ts @@ -0,0 +1,43 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +export { + file_agntcy_dir_store_v1_store_service, + PushReferrerRequest, + PushReferrerRequestSchema, + PushReferrerResponse, + PushReferrerResponseSchema, + PullReferrerRequest, + PullReferrerRequestSchema, + PullReferrerResponse, + PullReferrerResponseSchema, + StoreService, +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/store/v1/store_service_pb'; +export { + file_agntcy_dir_store_v1_sync_service, + CreateSyncRequest, + CreateSyncRequestSchema, + CreateSyncResponse, + CreateSyncResponseSchema, + ListSyncsRequest, + ListSyncsRequestSchema, + ListSyncsItem, + ListSyncsItemSchema, + GetSyncRequest, + GetSyncRequestSchema, + GetSyncResponse, + GetSyncResponseSchema, + DeleteSyncRequest, + DeleteSyncRequestSchema, + DeleteSyncResponse, + DeleteSyncResponseSchema, + RequestRegistryCredentialsRequest, + RequestRegistryCredentialsRequestSchema, + RequestRegistryCredentialsResponse, + RequestRegistryCredentialsResponseSchema, + BasicAuthCredentials, + BasicAuthCredentialsSchema, + SyncStatus, + SyncStatusSchema, + SyncService, +} from '@buf/agntcy_dir.bufbuild_es/agntcy/dir/store/v1/sync_service_pb'; diff --git a/test/client.test.ts b/test/client.test.ts new file mode 100644 index 0000000..55d5f12 --- /dev/null +++ b/test/client.test.ts @@ -0,0 +1,669 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +import { describe, test, beforeAll, afterAll, expect } from 'vitest'; + +import { execFileSync } from 'node:child_process'; +import { pool as workerpool } from 'workerpool'; +import { rmSync, realpathSync } from 'node:fs'; +import { env } from 'node:process'; +import { create } from '@bufbuild/protobuf'; + +import { validate as isValidUUID } from 'uuid'; +import { v4 as uuidv4 } from 'uuid'; + +import { Client, Config, models } from '../src'; + +/** + * Generate test records with unique names. + * Schema: https://schema.oasf.outshift.com/0.7.0/objects/record + * @param count - Number of records to generate + * @param testFunctionName - Name of the test function for record naming + * @returns Array of generated Record objects + */ +function genRecords( + count: number, + testFunctionName: string, +): models.core_v1.Record[] { + const records: models.core_v1.Record[] = []; + for (let index = 0; index < count; index++) { + records.push( + create(models.core_v1.RecordSchema, { + data: { + name: `agntcy-${testFunctionName}-${index}-${uuidv4().substring(0, 8)}`, + version: 'v3.0.0', + schema_version: '0.7.0', + description: "Research agent for Cisco's marketing strategy.", + authors: ['Cisco Systems'], + created_at: '2025-03-19T17:06:37Z', + skills: [ + { + name: 'natural_language_processing/natural_language_generation/text_completion', + id: 10201, + }, + { + name: 'natural_language_processing/analytical_reasoning/problem_solving', + id: 10702, + }, + ], + locators: [ + { + type: 'docker_image', + url: 'https://ghcr.io/agntcy/marketing-strategy', + }, + ], + domains: [ + { + name: 'technology/networking', + id: 103, + }, + ], + modules: [], + }, + }), + ); + } + + return records; +} + +describe('Client', () => { + let config: Config; + let client: Client; + + beforeAll(async () => { + // Initialize the client + config = Config.loadFromEnv(); + const grpcTransport = await Client.createGRPCTransport(config); + + client = new Client(config, grpcTransport); + }); + + afterAll(async () => { + // Clean up any resources if needed + // Note: gRPC clients in Connect don't need explicit closing + }); + + test('push', async () => { + const records = genRecords(2, 'push'); + const recordRefs = await client.push(records); + + expect(recordRefs).not.toBeNull(); + expect(recordRefs).toBeInstanceOf(Array); + expect(recordRefs).toHaveLength(2); + + for (const ref of recordRefs) { + expect(ref).toBeTypeOf(typeof models.core_v1.RecordRefSchema); + expect(ref.cid).toHaveLength(59); + } + }); + + test('pull', async () => { + const records = genRecords(2, 'pull'); + const recordRefs = await client.push(records); + const pulledRecords = await client.pull(recordRefs); + + expect(pulledRecords).not.toBeNull(); + expect(pulledRecords).toBeInstanceOf(Array); + expect(pulledRecords).toHaveLength(2); + + for (let index = 0; index < pulledRecords.length; index++) { + const record = pulledRecords[index]; + expect(record).toBeTypeOf(typeof models.core_v1.RecordSchema); + expect(record).toEqual(records[index]); + } + }); + + test('searchCIDs', async () => { + const records = genRecords(1, 'search'); + await client.push(records); + + const searchRequest = create(models.search_v1.SearchCIDsRequestSchema, { + queries: [ + { + type: models.search_v1.RecordQueryType.SKILL_ID, + value: '10201', + }, + ], + limit: 2, + }); + + const objects = await client.searchCIDs(searchRequest); + + expect(objects).not.toBeNull(); + expect(objects).toBeInstanceOf(Array); + expect(objects.length).toBeGreaterThan(0); + + for (const obj of objects) { + expect(obj).toHaveProperty('recordCid'); + } + }); + + test('lookup', async () => { + const records = genRecords(2, 'lookup'); + const recordRefs = await client.push(records); + const metadatas = await client.lookup(recordRefs); + + expect(metadatas).not.toBeNull(); + expect(metadatas).toBeInstanceOf(Array); + expect(metadatas).toHaveLength(2); + + for (const metadata of metadatas) { + expect(metadata).toBeTypeOf(typeof models.core_v1.RecordMetaSchema); + } + }); + + test('publish', async () => { + const records = genRecords(1, 'publish'); + const recordRefs = await client.push(records); + + await client.publish( + create(models.routing_v1.PublishRequestSchema, { + request: { + case: 'recordRefs', + value: { + refs: recordRefs, + }, + }, + }), + ); + }); + + test('list', async () => { + const records = genRecords(1, 'list'); + const recordRefs = await client.push(records); + + // Publish records + await client.publish( + create(models.routing_v1.PublishRequestSchema, { + request: { + case: 'recordRefs', + value: { + refs: recordRefs, + }, + }, + }), + ); + + // Sleep to allow the publication to be indexed + await new Promise(resolve => setTimeout(resolve, 5000)); + + // Query for records in the domain + const objects = await client.list( + create(models.routing_v1.ListRequestSchema, { + queries: [ + { + type: models.routing_v1.RecordQueryType.DOMAIN, + value: 'technology/networking', + }, + ], + }), + ); + + expect(objects).not.toBeNull(); + expect(objects).toBeInstanceOf(Array); + expect(objects.length).not.toBe(0); + + for (const obj of objects) { + expect(obj).toBeTypeOf(typeof models.routing_v1.ListResponseSchema); + } + }, 30000); + + test('unpublish', async () => { + const records = genRecords(1, 'unpublish'); + const recordRefs = await client.push(records); + + // Publish records + await client.publish( + create(models.routing_v1.PublishRequestSchema, { + request: { + case: 'recordRefs', + value: { + refs: recordRefs, + }, + }, + }), + ); + + // Unpublish + await client.unpublish( + create(models.routing_v1.UnpublishRequestSchema, { + request: { + case: 'recordRefs', + value: { + refs: recordRefs, + }, + }, + }), + ); + }); + + test('delete', async () => { + const records = genRecords(1, 'delete'); + const recordRefs = await client.push(records); + + await client.delete(recordRefs); + }); + + test('pushReferrer', async () => { + const records = genRecords(2, 'pushReferrer'); + const recordRefs = await client.push(records); + + const requests: models.store_v1.PushReferrerRequest[] = recordRefs.map( + ( + recordRef: models.core_v1.RecordRef, + ): models.store_v1.PushReferrerRequest => { + return create(models.store_v1.PushReferrerRequestSchema, { + recordRef: recordRef, + type: models.sign_v1.SignatureSchema.typeName, + data: { + signature: 'dGVzdC1zaWduYXR1cmU=', + annotations: { + payload: 'test-payload-data', + }, + }, + }); + }, + ); + + const response = await client.push_referrer(requests); + expect(response).not.toBeNull(); + expect(response).toHaveLength(2); + + for (const r of response) { + expect(r).toBeTypeOf(typeof models.store_v1.PushReferrerResponseSchema); + } + }); + + test('pullReferrer', async () => { + const records = genRecords(2, 'pullReferrer'); + const recordRefs = await client.push(records); + + // Push signatures to these records first + const pushRequests: models.store_v1.PushReferrerRequest[] = recordRefs.map( + ( + recordRef: models.core_v1.RecordRef, + ): models.store_v1.PushReferrerRequest => { + return create(models.store_v1.PushReferrerRequestSchema, { + recordRef: recordRef, + type: models.sign_v1.SignatureSchema.typeName, + data: { + signature: 'dGVzdC1zaWduYXR1cmU=', + annotations: { + payload: 'test-payload-data', + }, + }, + }); + }, + ); + + const pushResponse = await client.push_referrer(pushRequests); + expect(pushResponse).not.toBeNull(); + expect(pushResponse).toHaveLength(2); + + for (const r of pushResponse) { + expect(r).toBeTypeOf(typeof models.store_v1.PushReferrerResponseSchema); + } + + // Now pull the signatures back + const requests: models.store_v1.PullReferrerRequest[] = recordRefs.map( + ( + recordRef: models.core_v1.RecordRef, + ): models.store_v1.PullReferrerRequest => { + return create(models.store_v1.PullReferrerRequestSchema, { + recordRef: recordRef, + referrerType: models.sign_v1.SignatureSchema.typeName, + }); + }, + ); + + const response = await client.pull_referrer(requests); + expect(response).not.toBeNull(); + expect(response).toHaveLength(2); + + for (const r of response) { + expect(r).toBeTypeOf(typeof models.store_v1.PullReferrerResponseSchema); + } + }); + + test('sign_and_verify', async () => { + const shellEnv = { ...env }; + + const records = genRecords(2, 'sign_verify'); + const recordRefs = await client.push(records); + + const keyPassword = 'testing-key'; + + // Clean up any existing keys + rmSync('cosign.key', { force: true }); + rmSync('cosign.pub', { force: true }); + + try { + // Generate key pair + const cosignPath = env['COSIGN_PATH'] || 'cosign'; + execFileSync(cosignPath, ["generate-key-pair"], { + env: { ...shellEnv, COSIGN_PASSWORD: keyPassword }, + encoding: 'utf8', + stdio: 'pipe', + }); + + if (config.dockerConfig) { + const cosignKeyPath = realpathSync("cosign.key"); + const cosignPubPath = realpathSync("cosign.pub"); + config.dockerConfig.mounts.push(`type=bind,src=${cosignKeyPath},dst=/cosign.key`); + config.dockerConfig.mounts.push(`type=bind,src=${cosignPubPath},dst=/cosign.pub`); + } + + // Read configuration data + const token = shellEnv['OIDC_TOKEN'] || ''; + const providerUrl = shellEnv['OIDC_PROVIDER_URL'] || ''; + const clientId = shellEnv['OIDC_CLIENT_ID'] || 'sigstore'; + + // Create signing providers using file path reference + // The CLI will load the key from the file path directly + const keyRequest = create(models.sign_v1.SignRequestSchema, { + recordRef: recordRefs[0], + provider: { + request: { + case: 'key', + value: { + privateKey: 'cosign.key', + password: Buffer.from(keyPassword, 'utf-8'), + }, + }, + }, + }); + + const oidcRequest = create(models.sign_v1.SignRequestSchema, { + recordRef: recordRefs[1], + provider: { + request: { + case: 'oidc', + value: { + idToken: token, + options: { + oidcClientId: clientId, + oidcProviderUrl: providerUrl, + }, + }, + }, + }, + }); + + // Sign test + client.sign(keyRequest); + + if (token !== '' && providerUrl !== '') { + client.sign(oidcRequest); + } else { + recordRefs.pop(); // NOTE: Drop the unsigned record if no OIDC tested + } + + // Verification is asynchronous (reconciler caches results). Wait for it to run. + await new Promise((r) => setTimeout(r, 8_000)); + + let verifyIndex = 0; + for (const ref of recordRefs) { + const response = await client.verify( + create(models.sign_v1.VerifyRequestSchema, { + recordRef: ref, + }), + ); + + expect(response.success).toBe(true); + + // Verify that signers array is present and not empty + expect(response.signers).toBeDefined(); + expect(response.signers.length).toBeGreaterThan(0); + + // For the first record (key-signed), verify key signer info + if (verifyIndex === 0) { + const signer = response.signers[0]; + expect(signer.type.case).toBe('key'); + if (signer.type.case === 'key') { + expect(signer.type.value.publicKey).toBeDefined(); + expect(signer.type.value.publicKey.length).toBeGreaterThan(0); + expect(signer.type.value.algorithm).toBeDefined(); + } + } + + // For OIDC-signed record, verify OIDC signer info + if (verifyIndex === 1 && token !== '' && providerUrl !== '') { + const signer = response.signers[0]; + expect(signer.type.case).toBe('oidc'); + if (signer.type.case === 'oidc') { + expect(signer.type.value.issuer).toBeDefined(); + expect(signer.type.value.subject).toBeDefined(); + } + } + + // Response with from-server (cached) must match local verification + const fromServerResponse = await client.verify( + create(models.sign_v1.VerifyRequestSchema, { + recordRef: ref, + fromServer: true, + }), + ); + expect(fromServerResponse.success).toBe(response.success); + expect(fromServerResponse.signers).toBeDefined(); + expect(fromServerResponse.signers.length).toBe(response.signers.length); + for (let i = 0; i < response.signers.length; i++) { + const rSigner = response.signers[i]; + const sSigner = fromServerResponse.signers[i]; + expect(rSigner).toBeDefined(); + expect(sSigner).toBeDefined(); + expect(sSigner!.type.case).toBe(rSigner!.type.case); + if (rSigner!.type.case === 'key' && sSigner!.type.case === 'key') { + expect(sSigner!.type.value.publicKey).toBe(rSigner!.type.value.publicKey); + expect(sSigner!.type.value.algorithm).toBe(rSigner!.type.value.algorithm); + } + if (rSigner!.type.case === 'oidc' && sSigner!.type.case === 'oidc') { + expect(sSigner!.type.value.issuer).toBe(rSigner!.type.value.issuer); + expect(sSigner!.type.value.subject).toBe(rSigner!.type.value.subject); + } + } + + verifyIndex++; + } + + // Test invalid CID + try { + client.sign( + create(models.sign_v1.SignRequestSchema, { + recordRef: { cid: 'invalid-cid' }, + provider: { + request: { + case: 'key', + value: { + privateKey: 'invalid-private-key', + password: Uint8Array.from([]), + }, + }, + }, + }), + ); + expect.fail('Should have thrown error for invalid CID'); + } catch (error) { + if (error instanceof Error) { + expect(error.message).toContain('failed to decode CID invalid-cid'); + } + } + } catch (error) { + expect.fail(`Sign and verify test failed: ${error}`); + } finally { + // Clean up keys + rmSync('cosign.key', { force: true }); + rmSync('cosign.pub', { force: true }); + } + }, 30000); + + test('sync', async () => { + // Create sync + const createResponse = await client.create_sync( + create(models.store_v1.CreateSyncRequestSchema, { + remoteDirectoryUrl: + env['DIRECTORY_SERVER_PEER1_ADDRESS'] || '0.0.0.0:8891', + }), + ); + expect(createResponse).toBeTypeOf( + typeof models.store_v1.CreateSyncResponseSchema, + ); + + const syncId = createResponse.syncId; + expect(isValidUUID(syncId)).toBe(true); + + // List syncs + const listResponse = await client.list_syncs( + create(models.store_v1.ListSyncsRequestSchema, {}), + ); + expect(listResponse).toBeInstanceOf(Array); + + for (const syncItem of listResponse) { + expect(syncItem).toBeTypeOf(typeof models.store_v1.ListSyncsItemSchema); + expect(isValidUUID(syncItem.syncId)).toBe(true); + } + + // Get sync + const getResponse = await client.get_sync( + create(models.store_v1.GetSyncRequestSchema, { + syncId: syncId, + }), + ); + expect(getResponse).toBeTypeOf( + typeof models.store_v1.GetSyncResponseSchema, + ); + expect(getResponse.syncId).toEqual(syncId); + + // Delete sync + await client.delete_sync( + create(models.store_v1.DeleteSyncRequestSchema, { + syncId: syncId, + }), + ); + }); + + test('listen', async () => { + const records = genRecords(1, 'listen'); + const recordRefs = await client.push(records); + + const pool = workerpool(__dirname + '/listen_worker.ts'); + let args = ["pull", recordRefs[0].cid]; + + if (config.spiffeEndpointSocket !== '') { + args.push(...["--spiffe-socket-path", config.spiffeEndpointSocket]); + } + + const [command, commandArgs] = config.getCommandAndArgs(args) + + try { + pool.exec('pullRecordsBackground', [command, commandArgs]); + } catch (error) { + expect.fail(`pullRecordsBackground execution failed: ${error}`) + } + + let events = client.listen( + create(models.events_v1.ListenRequestSchema, {}) + ); + + for await (const response of events) { + expect(response).toBeTypeOf(typeof models.events_v1.ListenResponseSchema); + break; // Exit after first event for test purposes + } + + pool.terminate(true); + + }, 20000); + + test('publication', async () => { + const records = genRecords(1, 'publication'); + const recordRefs = await client.push(records); + + const createResponse = await client.create_publication( + create(models.routing_v1.PublishRequestSchema, { + request: { + case: 'recordRefs', + value: { + refs: recordRefs, + }, + }, + }), + ); + + expect(createResponse).toBeTypeOf( + typeof models.routing_v1.CreatePublicationResponseSchema, + ); + + const publicationsList = await client.list_publication( + create(models.routing_v1.ListPublicationsRequestSchema, {}), + ); + + expect(publicationsList).toBeInstanceOf(Array); + + for (const publication of publicationsList) { + expect(publication).toBeTypeOf(typeof models.routing_v1.ListPublicationsItemSchema); + } + + const getResponse = await client.get_publication( + create(models.routing_v1.GetPublicationRequestSchema, { + publicationId: createResponse.publicationId, + }), + ); + + expect(getResponse).toBeTypeOf( + typeof models.routing_v1.GetPublicationResponseSchema, + ); + + expect(getResponse.publicationId).toEqual(createResponse.publicationId); + }); + + test('resolve', async () => { + // Push a record using built-in generator + const records = genRecords(1, 'resolve'); + const recordName = records[0].data?.name as string; + const recordVersion = records[0].data?.version as string; + + const recordRefs = await client.push(records); + expect(recordRefs).toHaveLength(1); + + // Resolve by name + const resolveRequest = create(models.naming_v1.ResolveRequestSchema, { + name: recordName, + }); + const resolveResponse = await client.resolve(resolveRequest); + + expect(resolveResponse).not.toBeNull(); + expect(resolveResponse.records).toBeInstanceOf(Array); + expect(resolveResponse.records.length).toBeGreaterThan(0); + expect(resolveResponse.records[0].cid).toEqual(recordRefs[0].cid); + expect(resolveResponse.records[0].name).toEqual(recordName); + expect(resolveResponse.records[0].version).toEqual(recordVersion); + + // Resolve by name with version + const resolveWithVersionRequest = create(models.naming_v1.ResolveRequestSchema, { + name: recordName, + version: recordVersion, + }); + const resolveWithVersionResponse = await client.resolve(resolveWithVersionRequest); + + expect(resolveWithVersionResponse).not.toBeNull(); + expect(resolveWithVersionResponse.records).toHaveLength(1); + expect(resolveWithVersionResponse.records[0].cid).toEqual(recordRefs[0].cid); + }); + + test('getVerificationInfo', async () => { + // Push a record + const records = genRecords(1, 'verification'); + const recordRefs = await client.push(records); + + // Get verification info by CID (record is not signed, so it should return unverified) + const verifyByCidRequest = create(models.naming_v1.GetVerificationInfoRequestSchema, { + cid: recordRefs[0].cid, + }); + const verifyByCidResponse = await client.getVerificationInfo(verifyByCidRequest); + + expect(verifyByCidResponse).not.toBeNull(); + // Unsigned records should return verified=false + expect(verifyByCidResponse.verified).toBe(false); + expect(verifyByCidResponse.errorMessage).toBeDefined(); + }); +}); diff --git a/test/listen_worker.ts b/test/listen_worker.ts new file mode 100644 index 0000000..c4d6522 --- /dev/null +++ b/test/listen_worker.ts @@ -0,0 +1,21 @@ +import { spawnSync } from 'node:child_process'; +import { env } from 'node:process'; +import { worker } from 'workerpool'; + +worker({ + pullRecordsBackground, +}); + +export async function pullRecordsBackground(command: string, commandArgs: string[]) { + const shell_env = env; + + for (let count = 0; count < 90; count++) { + // Execute command + spawnSync( + command, commandArgs, + { env: { ...shell_env }, encoding: 'utf8', stdio: 'pipe' }, + ); + + await new Promise(resolve => setTimeout(resolve, 1000)); + } +} diff --git a/test/oidc-auth.test.ts b/test/oidc-auth.test.ts new file mode 100644 index 0000000..ecc2a36 --- /dev/null +++ b/test/oidc-auth.test.ts @@ -0,0 +1,223 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { env } from 'node:process'; + +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + +import { Client, Config, TokenCache, TOKEN_CACHE_FILE } from '../src/client/client.js'; +import * as oauthPkce from '../src/client/oauthPkce.js'; + +describe('OIDC auth config', () => { + const originalEnv = { ...env }; + + afterEach(() => { + for (const key of Object.keys(env)) { + if (!(key in originalEnv)) { + delete env[key]; + } + } + for (const [k, v] of Object.entries(originalEnv)) { + env[k] = v; + } + }); + + test('loadFromEnv uses AUTH_TOKEN', () => { + env['DIRECTORY_CLIENT_AUTH_TOKEN'] = 'primary-token'; + const config = Config.loadFromEnv(); + expect(config.authToken).toBe('primary-token'); + expect(config.oidcAccessToken).toBe('primary-token'); + }); + + test('loadFromEnv ignores legacy OIDC/OAUTH access token env names', () => { + env['DIRECTORY_CLIENT_OIDC_ACCESS_TOKEN'] = 'legacy-token'; + env['DIRECTORY_CLIENT_OAUTH_ACCESS_TOKEN'] = 'older-legacy-token'; + const config = Config.loadFromEnv(); + expect(config.authToken).toBe(''); + expect(config.oidcAccessToken).toBe(''); + }); + + test('Config has no machine-flow OIDC fields', () => { + const config = new Config(); + expect('oidcMachineClientId' in config).toBe(false); + expect('oidcMachineClientSecret' in config).toBe(false); + }); + + test('token cache uses XDG_CONFIG_HOME dirctl path', () => { + const tmp = mkdtempSync(join(tmpdir(), 'dir-sdk-oidc-')); + env['XDG_CONFIG_HOME'] = tmp; + const cache = new TokenCache(); + expect(cache.getCachePath()).toBe(join(tmp, 'dirctl', TOKEN_CACHE_FILE)); + }); +}); + +describe('OIDC auth client', () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + test('constructor uses pre-issued token without calling discovery or login', () => { + const fetchSpy = vi.spyOn(oauthPkce, 'fetchOpenidConfiguration'); + const loginSpy = vi.spyOn(oauthPkce, 'runLoopbackPkceLogin'); + const cacheSpy = vi.spyOn(TokenCache.prototype, 'getValidToken').mockReturnValue(undefined); + + const config = new Config( + 'directory.example.com:443', + Config.DEFAULT_DIRCTL_PATH, + Config.DEFAULT_SPIFFE_ENDPOINT_SOCKET, + 'oidc', + Config.DEFAULT_JWT_AUDIENCE, + Config.DEFAULT_TLS_CA_FILE, + Config.DEFAULT_TLS_CERT_FILE, + Config.DEFAULT_TLS_KEY_FILE, + 'preissued-token', + ); + const client = new Client(config); + + expect(client['oauthHolder']?.getAccessToken()).toBe('preissued-token'); + expect(fetchSpy).not.toHaveBeenCalled(); + expect(loginSpy).not.toHaveBeenCalled(); + cacheSpy.mockRestore(); + }); + + test('constructor without token does not start PKCE', () => { + const fetchSpy = vi.spyOn(oauthPkce, 'fetchOpenidConfiguration'); + const loginSpy = vi.spyOn(oauthPkce, 'runLoopbackPkceLogin'); + vi.spyOn(TokenCache.prototype, 'getValidToken').mockReturnValue(undefined); + + const config = new Config( + 'directory.example.com:443', + Config.DEFAULT_DIRCTL_PATH, + Config.DEFAULT_SPIFFE_ENDPOINT_SOCKET, + 'oidc', + ); + const client = new Client(config); + + expect(() => client['oauthHolder']?.getAccessToken()).toThrow( + /DIRECTORY_CLIENT_AUTH_TOKEN/, + ); + expect(fetchSpy).not.toHaveBeenCalled(); + expect(loginSpy).not.toHaveBeenCalled(); + }); + + test('constructor uses cached token', () => { + vi.spyOn(oauthPkce, 'fetchOpenidConfiguration'); + vi.spyOn(oauthPkce, 'runLoopbackPkceLogin'); + const tmp = mkdtempSync(join(tmpdir(), 'dir-sdk-oidc-cache-')); + env['XDG_CONFIG_HOME'] = tmp; + const cacheDir = join(tmp, 'dirctl'); + mkdirSync(cacheDir, { recursive: true }); + const expiresAt = new Date(Date.now() + 3_600_000).toISOString().replace(/\.\d{3}Z$/, 'Z'); + const createdAt = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z'); + writeFileSync( + join(cacheDir, TOKEN_CACHE_FILE), + `${JSON.stringify( + { + access_token: 'cached-token', + token_type: 'bearer', + provider: 'oidc', + issuer: 'https://issuer.example.com', + refresh_token: 'cached-refresh-token', + expires_at: expiresAt, + created_at: createdAt, + }, + null, + 2, + )}\n`, + ); + + const config = new Config( + 'directory.example.com:443', + Config.DEFAULT_DIRCTL_PATH, + Config.DEFAULT_SPIFFE_ENDPOINT_SOCKET, + 'oidc', + ); + const client = new Client(config); + + expect(client['oauthHolder']?.getAccessToken()).toBe('cached-token'); + }); + + test('authenticateOAuthPkce updates access token', async () => { + const tmp = mkdtempSync(join(tmpdir(), 'dir-sdk-oidc-auth-')); + env['XDG_CONFIG_HOME'] = tmp; + + vi.spyOn(oauthPkce, 'fetchOpenidConfiguration').mockResolvedValue({ + authorization_endpoint: 'https://issuer.example.com/auth', + token_endpoint: 'https://issuer.example.com/token', + }); + vi.spyOn(oauthPkce, 'runLoopbackPkceLogin').mockResolvedValue({ + access_token: 'fresh-token', + refresh_token: 'ignored-refresh-token', + expires_in: 3600, + }); + + const config = new Config( + 'directory.example.com:443', + Config.DEFAULT_DIRCTL_PATH, + Config.DEFAULT_SPIFFE_ENDPOINT_SOCKET, + 'oidc', + Config.DEFAULT_JWT_AUDIENCE, + Config.DEFAULT_TLS_CA_FILE, + Config.DEFAULT_TLS_CERT_FILE, + Config.DEFAULT_TLS_KEY_FILE, + '', + Config.DEFAULT_TLS_SERVER_NAME, + Config.DEFAULT_TLS_SKIP_VERIFY, + 'https://issuer.example.com', + 'client-id', + ); + const client = new Client(config); + await client.authenticateOAuthPkce(); + + expect(client['oauthHolder']?.getAccessToken()).toBe('fresh-token'); + expect(oauthPkce.fetchOpenidConfiguration).toHaveBeenCalledOnce(); + expect(oauthPkce.runLoopbackPkceLogin).toHaveBeenCalledOnce(); + }); + + test('authenticateOAuthPkce saves cache entry compatible with tooling', async () => { + const tmp = mkdtempSync(join(tmpdir(), 'dir-sdk-oidc-save-')); + env['XDG_CONFIG_HOME'] = tmp; + + vi.spyOn(oauthPkce, 'fetchOpenidConfiguration').mockResolvedValue({ + authorization_endpoint: 'https://issuer.example.com/auth', + token_endpoint: 'https://issuer.example.com/token', + }); + vi.spyOn(oauthPkce, 'runLoopbackPkceLogin').mockResolvedValue({ + access_token: 'fresh-token', + refresh_token: 'refresh-token', + token_type: 'bearer', + expires_in: 3600, + }); + + const config = new Config( + 'directory.example.com:443', + Config.DEFAULT_DIRCTL_PATH, + Config.DEFAULT_SPIFFE_ENDPOINT_SOCKET, + 'oidc', + Config.DEFAULT_JWT_AUDIENCE, + Config.DEFAULT_TLS_CA_FILE, + Config.DEFAULT_TLS_CERT_FILE, + Config.DEFAULT_TLS_KEY_FILE, + '', + Config.DEFAULT_TLS_SERVER_NAME, + Config.DEFAULT_TLS_SKIP_VERIFY, + 'https://issuer.example.com', + 'client-id', + ); + const client = new Client(config); + await client.authenticateOAuthPkce(); + + const cachedToken = new TokenCache().load(); + expect(cachedToken).toBeDefined(); + expect(cachedToken!.accessToken).toBe('fresh-token'); + expect(cachedToken!.refreshToken).toBe('refresh-token'); + expect(cachedToken!.provider).toBe('oidc'); + expect(cachedToken!.issuer).toBe('https://issuer.example.com'); + expect(cachedToken!.tokenType).toBe('bearer'); + expect(cachedToken!.createdAt).toBeDefined(); + expect(cachedToken!.expiresAt).toBeDefined(); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..14f2d86 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,52 @@ +{ + "compilerOptions": { + "rootDir": ".", + "outDir": "./dist", + "moduleResolution": "bundler", + "module": "es2020", + "target": "es2018", + "lib": [ + // ES2017 for async/await + "ES2017", + // DOM for the fetch and streams API + "DOM", + // ES2018.AsyncIterable for AsyncIterator + "ES2018.AsyncIterable" + ], + "declaration": true, + "strict": true, + "resolveJsonModule": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "composite": true, + "forceConsistentCasingInFileNames": true, + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "pretty": true, + "sourceMap": true, + "stripInternal": true + }, + "include": [ + "src/**/*.ts", + "test/**/*.test.ts" + ], + "exclude": [ + "node_modules", + "dist" + ], + "ts-node": { + "compilerOptions": { + "module": "ES2020" + } + }, + "vitest": { + "test": { + "globals": true, + "environment": "node", + "include": [ + "test/**/*.test.ts" + ] + } + } +}