diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 0244578094..5f63253d51 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -95,6 +95,10 @@ body: options: - TypeScript - Python + - Java + - Rust + - Go + - .Net validations: required: true @@ -111,7 +115,7 @@ body: attributes: label: Cluster information description: | - Cluster information, cluster topology, number of shards, number of replicas, used data types. + Cluster information, cluster topology, number of shards, number of replicas, used data types. validations: required: false diff --git a/.github/json_matrices/engine-matrix.json b/.github/json_matrices/engine-matrix.json index f20f0c955e..bf755b782e 100644 --- a/.github/json_matrices/engine-matrix.json +++ b/.github/json_matrices/engine-matrix.json @@ -2,5 +2,9 @@ { "type": "valkey", "version": "7.2.5" + }, + { + "type": "valkey", + "version": "8.0.0-rc1" } ] diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index 129367a362..36b380c3e0 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -24,6 +24,7 @@ on: - .github/workflows/lint-rust/action.yml - .github/workflows/install-valkey/action.yml - .github/json_matrices/build-matrix.json + workflow_dispatch: permissions: contents: read diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 9b782d8fb7..7cdfedef59 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -24,11 +24,12 @@ on: - .github/workflows/lint-rust/action.yml - .github/workflows/install-valkey/action.yml - .github/json_matrices/build-matrix.json + workflow_dispatch: + concurrency: group: go-${{ github.head_ref || github.ref }} cancel-in-progress: true - jobs: load-engine-matrix: runs-on: ubuntu-latest @@ -51,7 +52,6 @@ jobs: fail-fast: false matrix: go: - # - '1.18.10' - '1.22.0' engine: ${{ fromJson(needs.load-engine-matrix.outputs.matrix) }} host: @@ -105,7 +105,22 @@ jobs: - name: Run tests working-directory: ./go - run: make test + run: | + make test + + - uses: ./.github/workflows/test-benchmark + with: + language-flag: -go + + - name: Upload logs and reports + if: always() + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: reports-go-${{ matrix.go }}-redis-${{ matrix.redis }}-${{ matrix.os }} + path: | + utils/clusters/** + benchmarks/results/** build-amazonlinux-latest: if: github.repository_owner == 'valkey-io' @@ -114,7 +129,6 @@ jobs: fail-fast: false matrix: go: - - 1.18.10 - 1.22.0 runs-on: ubuntu-latest container: amazonlinux:latest @@ -157,6 +171,9 @@ jobs: working-directory: ./go run: make install-tools-go${{ matrix.go }} + - name: Set LD_LIBRARY_PATH + run: echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GITHUB_WORKSPACE/go/target/release/deps/" >> $GITHUB_ENV + - name: Build client working-directory: ./go run: make build @@ -167,7 +184,17 @@ jobs: - name: Run tests working-directory: ./go - run: make test + run: | + make test + + - name: Upload cluster manager logs + if: always() + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: cluster-manager-logs-${{ matrix.go }}-redis-6-amazonlinux + path: | + utils/clusters/** lint-rust: timeout-minutes: 15 diff --git a/.github/workflows/install-shared-dependencies/action.yml b/.github/workflows/install-shared-dependencies/action.yml index af44e7206f..0a134eecc9 100644 --- a/.github/workflows/install-shared-dependencies/action.yml +++ b/.github/workflows/install-shared-dependencies/action.yml @@ -63,7 +63,7 @@ runs: shell: bash if: "${{ inputs.os == 'amazon-linux' }}" run: | - yum install -y gcc pkgconfig openssl openssl-devel which curl gettext --allowerasing + yum install -y gcc pkgconfig openssl openssl-devel which curl gettext tar --allowerasing - name: Install Rust toolchain and protoc if: "${{ !contains(inputs.target, 'musl') }}" diff --git a/.github/workflows/install-valkey/action.yml b/.github/workflows/install-valkey/action.yml index f15a875c03..74c75572a4 100644 --- a/.github/workflows/install-valkey/action.yml +++ b/.github/workflows/install-valkey/action.yml @@ -62,6 +62,7 @@ runs: echo 'export PATH=/usr/local/bin:$PATH' >>~/.bash_profile - name: Verify Valkey installation and symlinks + if: ${{ !contains(inputs.engine-version, '-rc') }} shell: bash run: | # In Valkey releases, the engine is built with symlinks from valkey-server and valkey-cli diff --git a/.github/workflows/java-cd.yml b/.github/workflows/java-cd.yml index 1dba670476..0859892b45 100644 --- a/.github/workflows/java-cd.yml +++ b/.github/workflows/java-cd.yml @@ -56,26 +56,21 @@ jobs: OS: ubuntu, RUNNER: ubuntu-latest, TARGET: x86_64-unknown-linux-gnu, - CLASSIFIER: linux-x86_64 } - { OS: ubuntu, RUNNER: ["self-hosted", "Linux", "ARM64"], TARGET: aarch64-unknown-linux-gnu, - CLASSIFIER: linux-aarch_64, - CONTAINER: "2_28" } - { OS: macos, RUNNER: macos-12, TARGET: x86_64-apple-darwin, - CLASSIFIER: osx-x86_64 } - { OS: macos, RUNNER: macos-latest, TARGET: aarch64-apple-darwin, - CLASSIFIER: osx-aarch_64 } runs-on: ${{ matrix.host.RUNNER }} @@ -99,7 +94,7 @@ jobs: - name: Set the release version shell: bash run: | - if ${{ github.event_name == 'pull_request' || github.event_name == 'push' }}; then + if ${{ github.event_name == 'pull_request' }}; then R_VERSION="255.255.255" elif ${{ github.event_name == 'workflow_dispatch' }}; then R_VERSION="${{ env.INPUT_VERSION }}" @@ -139,18 +134,12 @@ jobs: env: SECRING_GPG: ${{ secrets.SECRING_GPG }} - - name: Replace placeholders and version in build.gradle - shell: bash - working-directory: ./java/client - run: | - SED_FOR_MACOS=`if [[ "${{ matrix.host.os }}" =~ .*"macos".* ]]; then echo "''"; fi` - sed -i $SED_FOR_MACOS 's/placeholder/${{ matrix.host.CLASSIFIER }}/g' build.gradle - sed -i $SED_FOR_MACOS "s/255.255.255/${{ env.RELEASE_VERSION }}/g" build.gradle - - name: Build java client working-directory: java run: | ./gradlew :client:publishToMavenLocal -Psigning.secretKeyRingFile=secring.gpg -Psigning.password="${{ secrets.GPG_PASSWORD }}" -Psigning.keyId=${{ secrets.GPG_KEY_ID }} + env: + GLIDE_RELEASE_VERSION: ${{ env.RELEASE_VERSION }} - name: Bundle JAR working-directory: java @@ -160,7 +149,7 @@ jobs: jar -cvf bundle.jar * ls -ltr cd - - cp $src_folder/bundle.jar . + cp $src_folder/bundle.jar bundle-${{ matrix.host.TARGET }}.jar - name: Upload artifacts to publish continue-on-error: true @@ -168,4 +157,4 @@ jobs: with: name: java-${{ matrix.host.TARGET }} path: | - java/bundle.jar + java/bundle*.jar diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml index 2e19a91a85..5106aec4ce 100644 --- a/.github/workflows/java.yml +++ b/.github/workflows/java.yml @@ -24,6 +24,7 @@ on: - .github/workflows/lint-rust/action.yml - .github/workflows/install-valkey/action.yml - .github/json_matrices/build-matrix.json + workflow_dispatch: concurrency: group: java-${{ github.head_ref || github.ref }} @@ -97,7 +98,7 @@ jobs: - name: Build java client working-directory: java - run: ./gradlew --continue build + run: ./gradlew --continue build -x javadoc - name: Ensure no skipped files by linter working-directory: java @@ -165,22 +166,18 @@ jobs: - name: Install Java run: | - yum install -y java-${{ matrix.java }} + yum install -y java-${{ matrix.java }}-amazon-corretto-devel.x86_64 - - name: Build rust part + - name: Build java wrapper working-directory: java - run: cargo build --release - - - name: Build java part - working-directory: java - run: ./gradlew --continue build + run: ./gradlew --continue build -x javadoc - name: Upload test & spotbugs reports if: always() continue-on-error: true uses: actions/upload-artifact@v4 with: - name: test-reports-${{ matrix.java }} + name: test-reports-${{ matrix.java }}-amazon-linux path: | java/client/build/reports/** java/integTest/build/reports/** diff --git a/.github/workflows/lint-rust/action.yml b/.github/workflows/lint-rust/action.yml index 8a7cdf185f..11ca944f71 100644 --- a/.github/workflows/lint-rust/action.yml +++ b/.github/workflows/lint-rust/action.yml @@ -42,7 +42,7 @@ runs: - run: | cargo update - cargo install cargo-deny + cargo install --locked --version 0.15.1 cargo-deny cargo deny check --config ${GITHUB_WORKSPACE}/deny.toml working-directory: ${{ inputs.cargo-toml-folder }} shell: bash diff --git a/.github/workflows/lint-ts.yml b/.github/workflows/lint-ts.yml index 23d6f348e3..72b51ba16c 100644 --- a/.github/workflows/lint-ts.yml +++ b/.github/workflows/lint-ts.yml @@ -14,6 +14,7 @@ on: - node/** - benchmarks/utilities/* - .github/workflows/lint-ts.yml + workflow_dispatch: concurrency: group: node-lint-${{ github.head_ref || github.ref }} diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index d452f05dcb..32db45e5c5 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -28,6 +28,7 @@ on: - .github/workflows/lint-rust/action.yml - .github/workflows/install-valkey/action.yml - .github/json_matrices/build-matrix.json + workflow_dispatch: concurrency: group: node-${{ github.head_ref || github.ref }} @@ -53,12 +54,12 @@ jobs: test-ubuntu-latest: runs-on: ubuntu-latest needs: load-engine-matrix - timeout-minutes: 15 + timeout-minutes: 25 strategy: fail-fast: false matrix: engine: ${{ fromJson(needs.load-engine-matrix.outputs.matrix) }} - + steps: - uses: actions/checkout@v4 with: @@ -84,21 +85,36 @@ jobs: - name: test hybrid node modules - commonjs run: | npm install --package-lock-only - npm ci + npm ci npm run build-and-test working-directory: ./node/hybrid-node-tests/commonjs-test - + env: + JEST_HTML_REPORTER_OUTPUT_PATH: test-report-commonjs.html + - name: test hybrid node modules - ecma run: | npm install --package-lock-only npm ci npm run build-and-test working-directory: ./node/hybrid-node-tests/ecmascript-test + env: + JEST_HTML_REPORTER_OUTPUT_PATH: test-report-ecma.html - uses: ./.github/workflows/test-benchmark with: language-flag: -node + - name: Upload test reports + if: always() + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: test-report-node-${{ matrix.engine.type }}-${{ matrix.engine.version }}-ubuntu + path: | + node/test-report*.html + utils/clusters/** + benchmarks/results/** + lint-rust: timeout-minutes: 15 runs-on: ubuntu-latest @@ -146,6 +162,17 @@ jobs: # run: npm test -- -t "set and get flow works" # working-directory: ./node + # - name: Upload test reports + # if: always() + # continue-on-error: true + # uses: actions/upload-artifact@v4 + # with: + # name: test-report-node-${{ matrix.engine.type }}-${{ matrix.engine.version }}-macos + # path: | + # node/test-report*.html + # utils/clusters/** + # benchmarks/results/** + build-amazonlinux-latest: runs-on: ubuntu-latest container: amazonlinux:latest @@ -156,7 +183,7 @@ jobs: yum -y remove git yum -y remove git-* yum -y install https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm - yum install -y git + yum install -y git git --version - uses: actions/checkout@v4 @@ -180,8 +207,19 @@ jobs: - name: Test compatibility run: npm test -- -t "set and get flow works" - working-directory: ./node - + working-directory: ./node + + - name: Upload test reports + if: always() + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: test-report-node-amazonlinux + path: | + node/test-report*.html + utils/clusters/** + benchmarks/results/** + build-and-test-linux-musl-on-x86: name: Build and test Node wrapper on Linux musl runs-on: ubuntu-latest @@ -190,33 +228,44 @@ jobs: options: --user root --privileged steps: - - name: Install git - run: | - apk update - apk add git - - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Setup musl on Linux - uses: ./.github/workflows/setup-musl-on-linux - with: - workspace: $GITHUB_WORKSPACE - npm-scope: ${{ secrets.NPM_SCOPE }} - npm-auth-token: ${{ secrets.NPM_AUTH_TOKEN }} - - - name: Build Node wrapper - uses: ./.github/workflows/build-node-wrapper - with: - os: ubuntu - named_os: linux - arch: x64 - target: x86_64-unknown-linux-musl - github-token: ${{ secrets.GITHUB_TOKEN }} - engine-version: "7.2.5" - - - name: Test compatibility - shell: bash - run: npm test -- -t "set and get flow works" - working-directory: ./node + - name: Install git + run: | + apk update + apk add git + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup musl on Linux + uses: ./.github/workflows/setup-musl-on-linux + with: + workspace: $GITHUB_WORKSPACE + npm-scope: ${{ secrets.NPM_SCOPE }} + npm-auth-token: ${{ secrets.NPM_AUTH_TOKEN }} + + - name: Build Node wrapper + uses: ./.github/workflows/build-node-wrapper + with: + os: ubuntu + named_os: linux + arch: x64 + target: x86_64-unknown-linux-musl + github-token: ${{ secrets.GITHUB_TOKEN }} + engine-version: "7.2.5" + + - name: Test compatibility + shell: bash + run: npm test -- -t "set and get flow works" + working-directory: ./node + + - name: Upload test reports + if: always() + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: test-report-node-linux-musl + path: | + node/test-report*.html + utils/clusters/** + benchmarks/results/** diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index f364c03da3..2511c8c1f4 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -15,6 +15,8 @@ on: - .github/workflows/lint-rust/action.yml - .github/workflows/install-valkey/action.yml - .github/json_matrices/build-matrix.json + - .github/json_matrices/engine-matrix.json + - .github/workflows/start-self-hosted-runner/action.yml pull_request: paths: @@ -29,6 +31,9 @@ on: - .github/workflows/lint-rust/action.yml - .github/workflows/install-valkey/action.yml - .github/json_matrices/build-matrix.json + - .github/json_matrices/engine-matrix.json + - .github/workflows/start-self-hosted-runner/action.yml + workflow_dispatch: concurrency: group: python-${{ github.head_ref || github.ref }} @@ -36,23 +41,25 @@ concurrency: permissions: contents: read + # Allows the GITHUB_TOKEN to make an API call to generate an OIDC token. + id-token: write jobs: load-engine-matrix: - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.load-engine-matrix.outputs.matrix }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Load the engine matrix - id: load-engine-matrix - shell: bash - run: echo "matrix=$(jq -c . < .github/json_matrices/engine-matrix.json)" >> $GITHUB_OUTPUT - - test-ubuntu-latest: runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.load-engine-matrix.outputs.matrix }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Load the engine matrix + id: load-engine-matrix + shell: bash + run: echo "matrix=$(jq -c . < .github/json_matrices/engine-matrix.json)" >> $GITHUB_OUTPUT + + test: + runs-on: ${{ matrix.host.RUNNER }} needs: load-engine-matrix timeout-minutes: 35 strategy: @@ -64,7 +71,18 @@ jobs: # - "3.9" # - "3.10" # - "3.11" - - "3.12" + - "3.12" + host: + - { + OS: ubuntu, + RUNNER: ubuntu-latest, + TARGET: x86_64-unknown-linux-gnu + } + # - { + # OS: macos, + # RUNNER: macos-latest, + # TARGET: aarch64-apple-darwin + # } steps: - uses: actions/checkout@v4 @@ -80,31 +98,13 @@ jobs: working-directory: ./python run: | python -m pip install --upgrade pip - pip install flake8 isort black mypy-protobuf - - - name: Lint with isort - working-directory: ./python - run: | - isort . --profile black --check --diff - - - name: Lint with flake8 - working-directory: ./python - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --extend-ignore=E230 --exclude=python/glide/protobuf,.env/* - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=12 --max-line-length=127 --statistics --extend-ignore=E230 --exclude=python/glide/protobuf,.env/* - - - name: Lint with black - working-directory: ./python - run: | - black --check --diff . + pip install mypy-protobuf - name: Build Python wrapper uses: ./.github/workflows/build-python-wrapper with: - os: "ubuntu" - target: "x86_64-unknown-linux-gnu" + os: ${{ matrix.host.OS }} + target: ${{ matrix.host.TARGET }} github-token: ${{ secrets.GITHUB_TOKEN }} engine-version: ${{ matrix.engine.version }} @@ -124,53 +124,84 @@ jobs: run: | source .env/bin/activate cd python/tests/ - pytest --asyncio-mode=auto + pytest --asyncio-mode=auto --html=pytest_report.html --self-contained-html - uses: ./.github/workflows/test-benchmark with: language-flag: -python - test-pubsub-ubuntu-latest: - runs-on: ubuntu-latest - needs: load-engine-matrix - timeout-minutes: 35 - strategy: - fail-fast: false - matrix: - engine: ${{ fromJson(needs.load-engine-matrix.outputs.matrix) }} - python: - # - "3.8" - # - "3.9" - # - "3.10" - # - "3.11" - - "3.12" - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python }} - - - name: Build Python wrapper - uses: ./.github/workflows/build-python-wrapper - with: - os: "ubuntu" - target: "x86_64-unknown-linux-gnu" - github-token: ${{ secrets.GITHUB_TOKEN }} - engine-version: ${{ matrix.engine.version }} + - name: Upload test reports + if: always() + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: test-report-python-${{ matrix.python }}-${{ matrix.engine.type }}-${{ matrix.engine.version }}-${{ matrix.host.RUNNER }} + path: | + python/python/tests/pytest_report.html + utils/clusters/** + benchmarks/results/** + + test-pubsub: + runs-on: ${{ matrix.host.RUNNER }} + needs: load-engine-matrix + timeout-minutes: 35 + strategy: + fail-fast: false + matrix: + engine: ${{ fromJson(needs.load-engine-matrix.outputs.matrix) }} + python: + # - "3.8" + # - "3.9" + # - "3.10" + # - "3.11" + - "3.12" + host: + - { + OS: ubuntu, + RUNNER: ubuntu-latest, + TARGET: x86_64-unknown-linux-gnu + } + # - { + # OS: macos, + # RUNNER: macos-latest, + # TARGET: aarch64-apple-darwin + # } - - name: Test pubsub with pytest - working-directory: ./python - run: | - source .env/bin/activate - cd python/tests/ - pytest --asyncio-mode=auto -k test_pubsub + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + + - name: Build Python wrapper + uses: ./.github/workflows/build-python-wrapper + with: + os: ${{ matrix.host.OS }} + target: ${{ matrix.host.TARGET }} + github-token: ${{ secrets.GITHUB_TOKEN }} + engine-version: ${{ matrix.engine.version }} + + - name: Test pubsub with pytest + working-directory: ./python + run: | + source .env/bin/activate + cd python/tests/ + pytest --asyncio-mode=auto -k test_pubsub --html=pytest_report.html --self-contained-html + + - name: Upload test reports + if: always() + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: pubsub-test-report-python-${{ matrix.python }}-${{ matrix.engine.type }}-${{ matrix.engine.version }}-${{ matrix.host.RUNNER }} + path: | + python/python/tests/pytest_report.html - lint-rust: + lint: runs-on: ubuntu-latest timeout-minutes: 15 steps: @@ -178,69 +209,38 @@ jobs: with: submodules: recursive - - uses: ./.github/workflows/lint-rust + - name: lint rust + uses: ./.github/workflows/lint-rust with: cargo-toml-folder: ./python - name: lint python-rust - - # test-macos-latest: - # runs-on: macos-latest - # needs: load-engine-matrix - # timeout-minutes: 35 - # strategy: - # fail-fast: false - # matrix: - # engine: ${{ fromJson(needs.load-engine-matrix.outputs.matrix) }} - # steps: - # - uses: actions/checkout@v4 - # with: - # submodules: recursive - # - name: Set up Homebrew - # uses: Homebrew/actions/setup-homebrew@master - - # - name: Build Python wrapper - # uses: ./.github/workflows/build-python-wrapper - # with: - # os: "macos" - # target: "aarch64-apple-darwin" - # github-token: ${{ secrets.GITHUB_TOKEN }} - # engine-version: ${{ matrix.engine.version }} - - # - name: Test with pytest - # working-directory: ./python - # run: | - # source .env/bin/activate - # pytest --asyncio-mode=auto - - # test-pubsub-macos-latest: - # runs-on: macos-latest - # needs: load-engine-matrix - # timeout-minutes: 35 - # strategy: - # fail-fast: false - # matrix: - # engine: ${{ fromJson(needs.load-engine-matrix.outputs.matrix) }} - # steps: - # - uses: actions/checkout@v4 - # with: - # submodules: recursive - # - name: Set up Homebrew - # uses: Homebrew/actions/setup-homebrew@master - - # - name: Build Python wrapper - # uses: ./.github/workflows/build-python-wrapper - # with: - # os: "macos" - # target: "aarch64-apple-darwin" - # github-token: ${{ secrets.GITHUB_TOKEN }} - # engine-version: ${{ matrix.engine.version }} - - # - name: Test pubsub with pytest - # working-directory: ./python - # run: | - # source .env/bin/activate - # cd python/tests/ - # pytest --asyncio-mode=auto -k test_pubsub + + - name: Install dependencies + if: always() + working-directory: ./python + run: | + python -m pip install --upgrade pip + pip install flake8 isort black + + - name: Lint python with isort + if: always() + working-directory: ./python + run: | + isort . --profile black --check --diff + + - name: Lint python with flake8 + if: always() + working-directory: ./python + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --extend-ignore=E230 --exclude=python/glide/protobuf,.env/* + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=12 --max-line-length=127 --statistics --extend-ignore=E230 --exclude=python/glide/protobuf,.env/* + + - name: Lint python with black + if: always() + working-directory: ./python + run: | + black --check --diff . build-amazonlinux-latest: runs-on: ubuntu-latest @@ -278,4 +278,86 @@ jobs: working-directory: ./python run: | source .env/bin/activate - pytest --asyncio-mode=auto -m smoke_test + pytest --asyncio-mode=auto -m smoke_test --html=pytest_report.html --self-contained-html + + - name: Upload test reports + if: always() + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: smoke-test-report-amazon-linux + path: | + python/python/tests/pytest_report.html + + start-self-hosted-runner: + if: github.event.pull_request.head.repo.owner.login == 'valkey-io' + runs-on: ubuntu-latest + environment: AWS_ACTIONS + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Start self hosted EC2 runner + uses: ./.github/workflows/start-self-hosted-runner + with: + role-to-assume: ${{ secrets.ROLE_TO_ASSUME }} + aws-region: ${{ secrets.AWS_REGION }} + ec2-instance-id: ${{ secrets.AWS_EC2_INSTANCE_ID }} + + test-modules: + needs: [start-self-hosted-runner, load-engine-matrix] + name: Running Module Tests + runs-on: ${{ matrix.host.RUNNER }} + timeout-minutes: 35 + strategy: + fail-fast: false + matrix: + engine: ${{ fromJson(needs.load-engine-matrix.outputs.matrix) }} + python: + - "3.12" + host: + - { + OS: "ubuntu", + NAMED_OS: "linux", + RUNNER: ["self-hosted", "Linux", "ARM64"], + TARGET: "aarch64-unknown-linux-gnu", + } + + steps: + - name: Setup self-hosted runner access + if: ${{ contains(matrix.host.RUNNER, 'self-hosted') }} + run: sudo chown -R $USER:$USER /home/ubuntu/actions-runner/_work/valkey-glide + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Python for self-hosted Ubuntu runners + run: | + sudo apt update -y + sudo apt upgrade -y + sudo apt install python3 python3-venv python3-pip -y + + - name: Build Python wrapper + uses: ./.github/workflows/build-python-wrapper + with: + os: ${{ matrix.host.OS }} + target: ${{ matrix.host.TARGET }} + github-token: ${{ secrets.GITHUB_TOKEN }} + engine-version: ${{ matrix.engine.version }} + + - name: Test with pytest + working-directory: ./python + run: | + source .env/bin/activate + cd python/tests/ + pytest --asyncio-mode=auto --tls --cluster-endpoints=${{ secrets.MEMDB_MODULES_ENDPOINT }} -k server_modules --html=pytest_report.html --self-contained-html + + - name: Upload test reports + if: always() + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: smoke-test-report-amazon-linux + path: | + python/python/tests/pytest_report.html diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 64a8fc7aa6..2211a04f0f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -22,6 +22,7 @@ on: - .github/workflows/install-shared-dependencies/action.yml - .github/workflows/install-valkey/action.yml - .github/json_matrices/build-matrix.json + workflow_dispatch: concurrency: group: rust-${{ github.head_ref || github.ref }} diff --git a/CHANGELOG.md b/CHANGELOG.md index e85848b0e6..58ba036b18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,117 @@ #### Changes +* Node: Added binary variant to sorted set commands - part 1 ([#2190](https://github.com/valkey-io/valkey-glide/pull/2190)) +* Node: Added binary variant to HASH commands ([#2194](https://github.com/valkey-io/valkey-glide/pull/2194)) +* Node: Added binary variant to server management commands ([#2179](https://github.com/valkey-io/valkey-glide/pull/2179)) +* Node: Added/updated binary variant to connection management commands and WATCH/UNWATCH ([#2160](https://github.com/valkey-io/valkey-glide/pull/2160)) +* Java: Fix docs for stream commands ([#2086](https://github.com/valkey-io/valkey-glide/pull/2086)) +* Node: Added binary variant to bitmap commands ([#2178](https://github.com/valkey-io/valkey-glide/pull/2178)) +* Node: Added binary variant to generic commands ([#2158](https://github.com/valkey-io/valkey-glide/pull/2158)) +* Node: Added binary variant to geo commands ([#2149](https://github.com/valkey-io/valkey-glide/pull/2149)) +* Node: Added binary variant to HYPERLOGLOG commands ([#2176](https://github.com/valkey-io/valkey-glide/pull/2176)) +* Node: Added FUNCTION DUMP and FUNCTION RESTORE commands ([#2129](https://github.com/valkey-io/valkey-glide/pull/2129), [#2173](https://github.com/valkey-io/valkey-glide/pull/2173)) +* Node: Added binary variant to FUNCTION commands ([#2172](https://github.com/valkey-io/valkey-glide/pull/2172)) +* Node: Added ZUNIONSTORE command ([#2145](https://github.com/valkey-io/valkey-glide/pull/2145)) +* Node: Added XREADGROUP command ([#2124](https://github.com/valkey-io/valkey-glide/pull/2124)) +* Node: Added XINFO GROUPS command ([#2122](https://github.com/valkey-io/valkey-glide/pull/2122)) +* Java: Added PUBSUB CHANNELS, NUMPAT and NUMSUB commands ([#2105](https://github.com/valkey-io/valkey-glide/pull/2105)) +* Java: Added binary support for custom command ([#2109](https://github.com/valkey-io/valkey-glide/pull/2109)) +* Node: Added SSCAN command ([#2132](https://github.com/valkey-io/valkey-glide/pull/2132)) +* Node: Added HKEYS command ([#2136](https://github.com/aws/glide-for-redis/pull/2136)) +* Node: Added FUNCTION KILL command ([#2114](https://github.com/valkey-io/valkey-glide/pull/2114)) +* Node: Update all commands to use `async` ([#2110](https://github.com/valkey-io/valkey-glide/pull/2110)) +* Node: Added XAUTOCLAIM command ([#2108](https://github.com/valkey-io/valkey-glide/pull/2108)) +* Node: Added XPENDING commands ([#2085](https://github.com/valkey-io/valkey-glide/pull/2085)) +* Node: Added HSCAN command ([#2098](https://github.com/valkey-io/valkey-glide/pull/2098/)) +* Node: Added XINFO CONSUMERS command ([#2093](https://github.com/valkey-io/valkey-glide/pull/2093)) +* Node: Added HRANDFIELD command ([#2096](https://github.com/valkey-io/valkey-glide/pull/2096)) +* Node: Added FUNCTION STATS commands ([#2082](https://github.com/valkey-io/valkey-glide/pull/2082)) +* Node: Added XCLAIM command ([#2092](https://github.com/valkey-io/valkey-glide/pull/2092)) +* Node: Added EXPIRETIME and PEXPIRETIME commands ([#2063](https://github.com/valkey-io/valkey-glide/pull/2063)) +* Node: Added SORT commands ([#2028](https://github.com/valkey-io/valkey-glide/pull/2028)) +* Node: Added LASTSAVE command ([#2059](https://github.com/valkey-io/valkey-glide/pull/2059)) +* Node: Added GEOSEARCHSTORE command ([#2080](https://github.com/valkey-io/valkey-glide/pull/2080)) +* Node: Added LCS command ([#2049](https://github.com/valkey-io/valkey-glide/pull/2049)) +* Node: Added MSETNX command ([#2046](https://github.com/valkey-io/valkey-glide/pull/2046)) +* Node: Added BLMOVE command ([#2027](https://github.com/valkey-io/valkey-glide/pull/2027)) +* Node: Exported client configuration types ([#2023](https://github.com/valkey-io/valkey-glide/pull/2023)) +* Java, Python: Update docs for GEOSEARCH command ([#2017](https://github.com/valkey-io/valkey-glide/pull/2017)) +* Python: Update docs for BITFIELD and BITFIELD_RO commands ([#2048](https://github.com/valkey-io/valkey-glide/pull/2048)) +* Node: Added FUNCTION LIST command ([#2019](https://github.com/valkey-io/valkey-glide/pull/2019)) +* Node: Added GEOSEARCH command ([#2007](https://github.com/valkey-io/valkey-glide/pull/2007)) +* Node: Added LMOVE command ([#2002](https://github.com/valkey-io/valkey-glide/pull/2002)) +* Node: Added GEOPOS command ([#1991](https://github.com/valkey-io/valkey-glide/pull/1991)) +* Node: Added BITCOUNT command ([#1982](https://github.com/valkey-io/valkey-glide/pull/1982)) +* Node: Added BITPOS command ([#1998](https://github.com/valkey-io/valkey-glide/pull/1998)) +* Node: Added BITFIELD and BITFIELD_RO commands ([#2026](https://github.com/valkey-io/valkey-glide/pull/2026)) +* Node: Added TOUCH command ([#2055](https://github.com/valkey-io/valkey-glide/pull/2055)) +* Node: Added FLUSHDB command ([#1986](https://github.com/valkey-io/valkey-glide/pull/1986)) +* Node: Added GETDEL command ([#1968](https://github.com/valkey-io/valkey-glide/pull/1968)) +* Node: Added GETRANGE command ([#2079](https://github.com/valkey-io/valkey-glide/pull/2079)) +* Node: Added BITOP command ([#2012](https://github.com/valkey-io/valkey-glide/pull/2012)) +* Node: Added GETBIT command ([#1989](https://github.com/valkey-io/valkey-glide/pull/1989)) +* Node: Added SETBIT command ([#1978](https://github.com/valkey-io/valkey-glide/pull/1978)) +* Node: Added RANDOMKEY command ([#2057](https://github.com/valkey-io/valkey-glide/pull/2057)) +* Node: Added LPUSHX and RPUSHX command([#1959](https://github.com/valkey-io/valkey-glide/pull/1959)) * Node: Added LSET command ([#1952](https://github.com/valkey-io/valkey-glide/pull/1952)) * Node: Added SDIFFSTORE command ([#1931](https://github.com/valkey-io/valkey-glide/pull/1931)) +* Node: Added ZDIFF command ([#1972](https://github.com/valkey-io/valkey-glide/pull/1972)) +* Node: Added ZDIFFSTORE command ([#1985](https://github.com/valkey-io/valkey-glide/pull/1985)) +* Node: Added SINTERCARD command ([#1956](https://github.com/valkey-io/valkey-glide/pull/1956)) * Node: Added SINTERSTORE command ([#1929](https://github.com/valkey-io/valkey-glide/pull/1929)) * Node: Added SUNION command ([#1919](https://github.com/valkey-io/valkey-glide/pull/1919)) +* Node: Added SMISMEMBER command ([#1955](https://github.com/valkey-io/valkey-glide/pull/1955)) * Node: Added SDIFF command ([#1924](https://github.com/valkey-io/valkey-glide/pull/1924)) +* Node: Added ZMSCORE command ([#1987](https://github.com/valkey-io/valkey-glide/pull/1987)) +* Node: Added LOLWUT command ([#1934](https://github.com/valkey-io/valkey-glide/pull/1934)) +* Node: Added LPOS command ([#1927](https://github.com/valkey-io/valkey-glide/pull/1927)) +* Node: Added FUNCTION LOAD command ([#1969](https://github.com/valkey-io/valkey-glide/pull/1969)) +* Node: Added FUNCTION DELETE command ([#1990](https://github.com/valkey-io/valkey-glide/pull/1990)) +* Node: Added FUNCTION FLUSH command ([#1984](https://github.com/valkey-io/valkey-glide/pull/1984)) +* Node: Added FCALL and FCALL_RO commands ([#2011](https://github.com/valkey-io/valkey-glide/pull/2011)) +* Node: Added COPY command ([#2024](https://github.com/valkey-io/valkey-glide/pull/2024)) +* Node: Added MOVE command ([#2104](https://github.com/valkey-io/valkey-glide/pull/2104)) +* Node: Added ZMPOP command ([#1994](https://github.com/valkey-io/valkey-glide/pull/1994)) +* Node: Added ZINCRBY command ([#2009](https://github.com/valkey-io/valkey-glide/pull/2009)) +* Node: Added BZMPOP command ([#2018](https://github.com/valkey-io/valkey-glide/pull/2018)) +* Node: Added XRANGE command ([#2069](https://github.com/valkey-io/valkey-glide/pull/2069)) +* Node: Added XREVRANGE command ([#2148](https://github.com/valkey-io/valkey-glide/pull/2148)) +* Node: Added PFMERGE command ([#2053](https://github.com/valkey-io/valkey-glide/pull/2053)) +* Node: Added WATCH and UNWATCH commands ([#2076](https://github.com/valkey-io/valkey-glide/pull/2076)) +* Node: Added WAIT command ([#2113](https://github.com/valkey-io/valkey-glide/pull/2113)) +* Node: Added DUMP and RESTORE commands ([#2126](https://github.com/valkey-io/valkey-glide/pull/2126)) +* Node: Added transaction supports for DUMP and RESTORE ([#2159](https://github.com/valkey-io/valkey-glide/pull/2159)) +* Node: Added ZLEXCOUNT command ([#2022](https://github.com/valkey-io/valkey-glide/pull/2022)) +* Node: Added ZREMRANGEBYLEX command ([#2025](https://github.com/valkey-io/valkey-glide/pull/2025)) +* Node: Added ZRANGESTORE command ([#2068](https://github.com/valkey-io/valkey-glide/pull/2068)) +* Node: Added SRANDMEMBER command ([#2067](https://github.com/valkey-io/valkey-glide/pull/2067)) +* Node: Added XINFO STREAM command ([#2083](https://github.com/valkey-io/valkey-glide/pull/2083)) +* Node: Added ZSCAN command ([#2061](https://github.com/valkey-io/valkey-glide/pull/2061)) +* Node: Added SETRANGE command ([#2066](https://github.com/valkey-io/valkey-glide/pull/2066)) +* Node: Added APPEND command ([#2095](https://github.com/valkey-io/valkey-glide/pull/2095)) +* Node: Added XDEL command ([#2064](https://github.com/valkey-io/valkey-glide/pull/2064)) +* Node: Added LMPOP & BLMPOP command ([#2050](https://github.com/valkey-io/valkey-glide/pull/2050)) +* Node: Added PUBSUB support ([#1964](https://github.com/valkey-io/valkey-glide/pull/1964)) +* Node: Added PUBSUB * commands ([#2090](https://github.com/valkey-io/valkey-glide/pull/2090)) +* Python: Added PUBSUB * commands ([#2043](https://github.com/valkey-io/valkey-glide/pull/2043)) +* Node: Added XGROUP CREATE & XGROUP DESTROY commands ([#2084](https://github.com/valkey-io/valkey-glide/pull/2084)) +* Node: Added BZPOPMAX & BZPOPMIN command ([#2077](https://github.com/valkey-io/valkey-glide/pull/2077)) +* Node: Added XGROUP CREATECONSUMER & XGROUP DELCONSUMER commands ([#2088](https://github.com/valkey-io/valkey-glide/pull/2088)) +* Node: Added GETEX command ([#2107]((https://github.com/valkey-io/valkey-glide/pull/2107)) +* Node: Added ZINTER and ZUNION commands ([#2146](https://github.com/aws/glide-for-redis/pull/2146)) +* Node: Added XACK commands ([#2112](https://github.com/valkey-io/valkey-glide/pull/2112)) +* Node: Added XGROUP SETID command ([#2135]((https://github.com/valkey-io/valkey-glide/pull/2135)) +* Node: Added binary variant to string commands ([#2183](https://github.com/valkey-io/valkey-glide/pull/2183)) +* Node: Added binary variant to stream commands ([#2200](https://github.com/valkey-io/valkey-glide/pull/2200)) + +#### Breaking Changes +* Node: (Refactor) Convert classes to types ([#2005](https://github.com/valkey-io/valkey-glide/pull/2005)) +* Core: Change FUNCTION STATS command to return multi node response for standalone mode ([#2117](https://github.com/valkey-io/valkey-glide/pull/2117)) + +#### Fixes +* Java: Add overloads for XADD to allow duplicate entry keys ([#1970](https://github.com/valkey-io/valkey-glide/pull/1970)) +* Node: Fix ZADD bug where command could not be called with only the `changed` optional parameter ([#1995](https://github.com/valkey-io/valkey-glide/pull/1995)) +* Java: `XRange`/`XRevRange` should return `null` instead of `GlideException` when given a negative count ([#1920](https://github.com/valkey-io/valkey-glide/pull/1920)) +* Python: Fix `XClaim` return type to `List[bytes]` instead of `List[TEncodable]` ([#2075](https://github.com/valkey-io/valkey-glide/pull/2075)) ## 1.0.0 (2024-07-09) @@ -93,6 +201,14 @@ * Python: Added FUNCTION STATS command ([#1794](https://github.com/valkey-io/valkey-glide/pull/1794)) * Python: Added XINFO STREAM command ([#1816](https://github.com/valkey-io/valkey-glide/pull/1816)) * Python: Added transaction supports for DUMP, RESTORE, FUNCTION DUMP and FUNCTION RESTORE ([#1814](https://github.com/valkey-io/valkey-glide/pull/1814)) +* Node: Added FlushAll command ([#1958](https://github.com/valkey-io/valkey-glide/pull/1958)) +* Node: Added DBSize command ([#1932](https://github.com/valkey-io/valkey-glide/pull/1932)) +* Node: Added GeoAdd command ([#1980](https://github.com/valkey-io/valkey-glide/pull/1980)) +* Node: Added ZRevRank command ([#1977](https://github.com/valkey-io/valkey-glide/pull/1977)) +* Node: Added GeoDist command ([#1988](https://github.com/valkey-io/valkey-glide/pull/1988)) +* Node: Added GeoHash command ([#1997](https://github.com/valkey-io/valkey-glide/pull/1997)) +* Node: Added HStrlen command ([#2020](https://github.com/valkey-io/valkey-glide/pull/2020)) +* Node: Added ZRandMember command ([#2013](https://github.com/valkey-io/valkey-glide/pull/2013)) #### Breaking Changes * Node: Update XREAD to return a Map of Map ([#1494](https://github.com/valkey-io/valkey-glide/pull/1494)) diff --git a/benchmarks/install_and_test.sh b/benchmarks/install_and_test.sh index 5fb83801dc..ae50fb5e61 100755 --- a/benchmarks/install_and_test.sh +++ b/benchmarks/install_and_test.sh @@ -25,6 +25,7 @@ runPython=0 runNode=0 runCsharp=0 runJava=0 +runGo=0 runRust=0 concurrentTasks="1 10 100 1000" dataSize="100 4000" @@ -76,6 +77,12 @@ function runJavaBenchmark(){ ./gradlew :benchmarks:run --args="-resultsFile \"${BENCH_FOLDER}/$1\" --dataSize \"$2\" --concurrentTasks \"$concurrentTasks\" --clients \"$chosenClients\" --host $host $portFlag --clientCount \"$clientCount\" $tlsFlag $clusterFlag $minimalFlag" } +function runGoBenchmark(){ + cd ${BENCH_FOLDER}/../go/benchmarks + export LD_LIBRARY_PATH=${BENCH_FOLDER}/../go/target/release:$LD_LIBRARY_PATH + go run . -resultsFile ${BENCH_FOLDER}/$1 -dataSize $2 -concurrentTasks $concurrentTasks -clients $chosenClients -host $host $portFlag -clientCount $clientCount $tlsFlag $clusterFlag $minimalFlag +} + function runRustBenchmark(){ rustConcurrentTasks= for value in $concurrentTasks @@ -115,7 +122,7 @@ function resultFileName() { function Help() { echo Running the script without any arguments runs all benchmarks. - echo Pass -node, -csharp, -python, -java as arguments in order to run the node, csharp, python, or java benchmarks accordingly. + echo Pass -node, -csharp, -python, -java, -go as arguments in order to run the node, csharp, python, java, or go benchmarks accordingly. echo Multiple such flags can be passed. echo Pass -no-csv to skip analysis of the results. echo @@ -204,6 +211,15 @@ do runJava=1 chosenClients="Jedis" ;; + -go) + runAllBenchmarks=0 + runGo=1 + ;; + -go-redis) + runAllBenchmarks=0 + runGo=1 + chosenClients="go-redis" + ;; -csharp) runAllBenchmarks=0 runCsharp=1 @@ -274,6 +290,13 @@ do runJavaBenchmark $javaResults $currentDataSize fi + if [ $runAllBenchmarks == 1 ] || [ $runGo == 1 ]; + then + goResults=$(resultFileName go $currentDataSize) + resultFiles+=$goResults" " + runGoBenchmark $goResults $currentDataSize + fi + if [ $runAllBenchmarks == 1 ] || [ $runRust == 1 ]; then rustResults=$(resultFileName rust $currentDataSize) diff --git a/benchmarks/node/node_benchmark.ts b/benchmarks/node/node_benchmark.ts index 9c7e0b9772..a8ee7be6d8 100644 --- a/benchmarks/node/node_benchmark.ts +++ b/benchmarks/node/node_benchmark.ts @@ -8,7 +8,12 @@ import { parse } from "path"; import percentile from "percentile"; import { RedisClientType, createClient, createCluster } from "redis"; import { stdev } from "stats-lite"; -import { GlideClient, GlideClusterClient, Logger } from "valkey-glide"; +import { + GlideClient, + GlideClusterClient, + GlideString, + Logger, +} from "valkey-glide"; import { generateKeyGet, generateKeySet, @@ -34,8 +39,8 @@ const runningTasks: Promise[] = []; const benchJsonResults: object[] = []; interface IAsyncClient { - set: (key: string, value: string) => Promise; - get: (key: string) => Promise; + set: (key: string, value: string) => Promise; + get: (key: string) => Promise; } function chooseAction(): ChosenAction { diff --git a/benchmarks/utilities/csv_exporter.py b/benchmarks/utilities/csv_exporter.py index 2841e867f6..14004d0532 100755 --- a/benchmarks/utilities/csv_exporter.py +++ b/benchmarks/utilities/csv_exporter.py @@ -43,7 +43,7 @@ json_file_name = os.path.basename(json_file_full_path) - languages = ["csharp", "node", "python", "rust", "java"] + languages = ["csharp", "node", "python", "rust", "java", "go"] language = next( (language for language in languages if language in json_file_name), None ) diff --git a/csharp/DEVELOPER.md b/csharp/DEVELOPER.md index a5bb030474..f42e0d2022 100644 --- a/csharp/DEVELOPER.md +++ b/csharp/DEVELOPER.md @@ -80,12 +80,10 @@ source "$HOME/.cargo/env" Before starting this step, make sure you've installed all software requirments. 1. Clone the repository - -```bash -VERSION=0.1.0 # You can modify this to other released version or set it to "main" to get the unstable branch -git clone --branch ${VERSION} https://github.com/valkey-io/valkey-glide.git -cd valkey-glide -``` + ```bash + git clone https://github.com/valkey-io/valkey-glide.git + cd valkey-glide + ``` 2. Initialize git submodule diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000000..4c321f286b --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,51 @@ +### Kotlin ### +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### Scala ### +target +*.class + +### Node ### +*.js + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ +.bsp + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store diff --git a/examples/java/README.md b/examples/java/README.md index 0e46d8ba62..85d009df47 100644 --- a/examples/java/README.md +++ b/examples/java/README.md @@ -1,18 +1,21 @@ ## Run +Ensure that you have a server running on "localhost" on port "6379". To run the ClusterExample, make sure that the server has cluster mode enabled. If the server is running on a different host and/or port, update the StandaloneExample or ClusterExample with a configuration that matches your server settings. -Ensure that you have an instance of Valkey running on "localhost" on "6379". Otherwise, update glide.examples.ExamplesApp with a configuration that matches your server settings. - -To run the example: +To run the Standalone example: ``` cd valkey-glide/examples/java -./gradlew :run +./gradlew :runStandalone ``` - -You should expect to see the output: +To run the Cluster example: ``` -> Task :run -PING: PONG -PING(found you): found you -SET(apples, oranges): OK -GET(apples): oranges +cd valkey-glide/examples/java +./gradlew :runCluster +``` + +## Version +These examples are running `valkey-glide` version `1.+`. In order to change the version, update the following section in the `build.gradle` file: +```groovy +dependencies { + implementation "io.valkey:valkey-glide:1.+:${osdetector.classifier}" +} ``` diff --git a/examples/java/build.gradle b/examples/java/build.gradle index be87b0a3f8..6ff2785725 100644 --- a/examples/java/build.gradle +++ b/examples/java/build.gradle @@ -1,6 +1,5 @@ plugins { - // Apply the application plugin to add support for building a CLI application in Java. - id 'application' + id "java" id "com.google.osdetector" version "1.7.3" } @@ -11,10 +10,19 @@ repositories { } dependencies { - implementation group: 'io.valkey', name: 'valkey-glide', version: '1.0.0', classifier: osdetector.classifier + implementation "io.valkey:valkey-glide:1.+:${osdetector.classifier}" } -application { - // Define the main class for the application. - mainClass = 'glide.examples.ExamplesApp' +task runStandalone(type: JavaExec) { + group = 'application' + description = 'Run the standalone example' + classpath = sourceSets.main.runtimeClasspath + mainClass = 'glide.examples.StandaloneExample' +} + +task runCluster(type: JavaExec) { + group = 'application' + description = 'Run the cluster example' + classpath = sourceSets.main.runtimeClasspath + mainClass = 'glide.examples.ClusterExample' } diff --git a/examples/java/src/main/java/glide/examples/ClusterExample.java b/examples/java/src/main/java/glide/examples/ClusterExample.java new file mode 100644 index 0000000000..cc598b632a --- /dev/null +++ b/examples/java/src/main/java/glide/examples/ClusterExample.java @@ -0,0 +1,151 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.examples; + +import static glide.api.logging.Logger.Level.ERROR; +import static glide.api.logging.Logger.Level.INFO; +import static glide.api.logging.Logger.Level.WARN; +import static glide.api.logging.Logger.log; +import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleMultiNodeRoute.ALL_NODES; + +import glide.api.GlideClusterClient; +import glide.api.logging.Logger; +import glide.api.models.ClusterValue; +import glide.api.models.commands.InfoOptions; +import glide.api.models.configuration.GlideClusterClientConfiguration; +import glide.api.models.configuration.NodeAddress; +import glide.api.models.exceptions.ClosingException; +import glide.api.models.exceptions.ConnectionException; +import glide.api.models.exceptions.TimeoutException; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class ClusterExample { + + /** + * Creates and returns a GlideClusterClient instance. + * + *

This function initializes a GlideClusterClient with the provided list of nodes. + * The list may contain the address of one or more cluster nodes, and the client will + * automatically discover all nodes in the cluster. + * + * @return A GlideClusterClient connected to the discovered nodes. + * @throws CancellationException if the operation is cancelled. + * @throws ExecutionException if the client fails due to execution errors. + * @throws InterruptedException if the operation is interrupted. + */ + public static GlideClusterClient createClient(List nodeList) + throws CancellationException, ExecutionException, InterruptedException { + // Check `GlideClusterClientConfiguration` for additional options. + GlideClusterClientConfiguration config = + GlideClusterClientConfiguration.builder() + .addresses(nodeList) + // Enable this field if the servers are configured with TLS. + // .useTLS(true); + .build(); + + GlideClusterClient client = GlideClusterClient.createClient(config).get(); + return client; + } + + /** + * Executes the main logic of the application, performing basic operations such as SET, GET, PING, + * and INFO REPLICATION using the provided GlideClusterClient. + * + * @param client An instance of GlideClusterClient. + * @throws ExecutionException if an execution error occurs during operations. + * @throws InterruptedException if the operation is interrupted. + */ + public static void appLogic(GlideClusterClient client) + throws ExecutionException, InterruptedException { + + // Send SET and GET + CompletableFuture setResponse = client.set("foo", "bar"); + log(INFO, "app", "Set response is " + setResponse.get()); + + CompletableFuture getResponse = client.get("foo"); + log(INFO, "app", "Get response is " + getResponse.get()); + + // Send PING to all primaries (according to Valkey's PING request_policy) + CompletableFuture pong = client.ping(); + log(INFO, "app", "Ping response is " + pong.get()); + + // Send INFO REPLICATION with routing option to all nodes + ClusterValue infoResponse = + client + .info(InfoOptions.builder().section(InfoOptions.Section.REPLICATION).build(), ALL_NODES) + .get(); + log( + INFO, + "app", + "INFO REPLICATION responses from all nodes are " + infoResponse.getMultiValue()); + } + + /** + * Executes the application logic with exception handling. + * + * @throws ExecutionException if an execution error occurs during operations. + */ + private static void execAppLogic() throws ExecutionException { + + // Define list of nodes + List nodeList = + Collections.singletonList(NodeAddress.builder().host("localhost").port(6379).build()); + + while (true) { + try (GlideClusterClient client = createClient(nodeList)) { + appLogic(client); + return; + } catch (CancellationException e) { + log(ERROR, "glide", "Request cancelled: " + e.getMessage()); + throw e; + } catch (InterruptedException e) { + log(ERROR, "glide", "Client interrupted: " + e.getMessage()); + Thread.currentThread().interrupt(); // Restore interrupt status + throw new CancellationException("Client was interrupted."); + } catch (ExecutionException e) { + // All Glide errors will be handled as ExecutionException + if (e.getCause() instanceof ClosingException) { + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + if (e.getMessage().contains("NOAUTH")) { + log(ERROR, "glide", "Authentication error encountered: " + e.getMessage()); + throw e; + } else { + log(WARN, "glide", "Client has closed and needs to be re-created: " + e.getMessage()); + } + } else if (e.getCause() instanceof ConnectionException) { + // The client wasn't able to reestablish the connection within the given retries + log(ERROR, "glide", "Connection error encountered: " + e.getMessage()); + throw e; + } else if (e.getCause() instanceof TimeoutException) { + // A request timed out. You may choose to retry the execution based on your application's + // logic + log(ERROR, "glide", "Timeout encountered: " + e.getMessage()); + throw e; + } else { + log(ERROR, "glide", "Execution error encountered: " + e.getCause()); + throw e; + } + } + } + } + + /** + * The entry point of the cluster example. This method sets up the logger configuration and + * executes the main application logic. + * + * @param args Command-line arguments passed to the application. + * @throws ExecutionException if an error occurs during execution of the application logic. + */ + public static void main(String[] args) throws ExecutionException { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig(INFO); + // Optional - set the logger to write to a file + // Logger.setLoggerConfig(Logger.Level.INFO, file) + execAppLogic(); + } +} diff --git a/examples/java/src/main/java/glide/examples/ExamplesApp.java b/examples/java/src/main/java/glide/examples/ExamplesApp.java deleted file mode 100644 index 4a686786eb..0000000000 --- a/examples/java/src/main/java/glide/examples/ExamplesApp.java +++ /dev/null @@ -1,40 +0,0 @@ -/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ -package glide.examples; - -import glide.api.GlideClient; -import glide.api.models.configuration.GlideClientConfiguration; -import glide.api.models.configuration.NodeAddress; -import java.util.concurrent.ExecutionException; - -public class ExamplesApp { - - // main application entrypoint - public static void main(String[] args) { - runGlideExamples(); - } - - private static void runGlideExamples() { - String host = "localhost"; - Integer port = 6379; - boolean useSsl = false; - - GlideClientConfiguration config = - GlideClientConfiguration.builder() - .address(NodeAddress.builder().host(host).port(port).build()) - .useTLS(useSsl) - .build(); - - try (GlideClient client = GlideClient.createClient(config).get()) { - - System.out.println("PING: " + client.ping().get()); - System.out.println("PING(found you): " + client.ping("found you").get()); - - System.out.println("SET(apples, oranges): " + client.set("apples", "oranges").get()); - System.out.println("GET(apples): " + client.get("apples").get()); - - } catch (ExecutionException | InterruptedException e) { - System.out.println("Glide example failed with an exception: "); - e.printStackTrace(); - } - } -} diff --git a/examples/java/src/main/java/glide/examples/StandaloneExample.java b/examples/java/src/main/java/glide/examples/StandaloneExample.java new file mode 100644 index 0000000000..996e408e83 --- /dev/null +++ b/examples/java/src/main/java/glide/examples/StandaloneExample.java @@ -0,0 +1,138 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.examples; + +import static glide.api.logging.Logger.Level.ERROR; +import static glide.api.logging.Logger.Level.INFO; +import static glide.api.logging.Logger.Level.WARN; +import static glide.api.logging.Logger.log; + +import glide.api.GlideClient; +import glide.api.logging.Logger; +import glide.api.models.configuration.GlideClientConfiguration; +import glide.api.models.configuration.NodeAddress; +import glide.api.models.exceptions.ClosingException; +import glide.api.models.exceptions.ConnectionException; +import glide.api.models.exceptions.TimeoutException; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class StandaloneExample { + + /** + * Creates and returns a GlideClient instance. + * + *

This function initializes a GlideClient with the provided list of nodes. The + * list may contain either only primary node or a mix of primary and replica nodes. The + * GlideClient + * use these nodes to connect to the Standalone setup servers. + * + * @return A GlideClient connected to the provided node address. + * @throws CancellationException if the operation is cancelled. + * @throws ExecutionException if the client fails due to execution errors. + * @throws InterruptedException if the operation is interrupted. + */ + public static GlideClient createClient(List nodeList) + throws CancellationException, ExecutionException, InterruptedException { + // Check `GlideClientConfiguration` for additional options. + GlideClientConfiguration config = + GlideClientConfiguration.builder() + .addresses(nodeList) + // Enable this field if the servers are configured with TLS. + // .useTLS(true); + .build(); + + GlideClient client = GlideClient.createClient(config).get(); + return client; + } + + /** + * Executes the main logic of the application, performing basic operations such as SET, GET, and + * PING using the provided GlideClient. + * + * @param client An instance of GlideClient. + * @throws ExecutionException if an execution error occurs during operations. + * @throws InterruptedException if the operation is interrupted. + */ + public static void appLogic(GlideClient client) throws ExecutionException, InterruptedException { + + // Send SET and GET + CompletableFuture setResponse = client.set("foo", "bar"); + log(INFO, "app", "Set response is " + setResponse.get()); + + CompletableFuture getResponse = client.get("foo"); + log(INFO, "app", "Get response is " + getResponse.get()); + + // Send PING + CompletableFuture pong = client.ping(); + log(INFO, "app", "Ping response is " + pong.get()); + } + + /** + * Executes the application logic with exception handling. + * + * @throws ExecutionException if an execution error occurs during operations. + */ + private static void execAppLogic() throws ExecutionException { + + // Define list of nodes + List nodeList = + Collections.singletonList(NodeAddress.builder().host("localhost").port(6379).build()); + + while (true) { + try (GlideClient client = createClient(nodeList)) { + appLogic(client); + return; + } catch (CancellationException e) { + log(ERROR, "glide", "Request cancelled: " + e.getMessage()); + throw e; + } catch (InterruptedException e) { + log(ERROR, "glide", "Client interrupted: " + e.getMessage()); + Thread.currentThread().interrupt(); // Restore interrupt status + throw new CancellationException("Client was interrupted."); + } catch (ExecutionException e) { + // All Glide errors will be handled as ExecutionException + if (e.getCause() instanceof ClosingException) { + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + if (e.getMessage().contains("NOAUTH")) { + log(ERROR, "glide", "Authentication error encountered: " + e.getMessage()); + throw e; + } else { + log(WARN, "glide", "Client has closed and needs to be re-created: " + e.getMessage()); + } + } else if (e.getCause() instanceof ConnectionException) { + // The client wasn't able to reestablish the connection within the given retries + log(ERROR, "glide", "Connection error encountered: " + e.getMessage()); + throw e; + } else if (e.getCause() instanceof TimeoutException) { + // A request timed out. You may choose to retry the execution based on your application's + // logic + log(ERROR, "glide", "Timeout encountered: " + e.getMessage()); + throw e; + } else { + log(ERROR, "glide", "Execution error encountered: " + e.getCause()); + throw e; + } + } + } + } + + /** + * The entry point of the standalone example. This method sets up the logger configuration and + * executes the main application logic. + * + * @param args Command-line arguments passed to the application. + * @throws ExecutionException if an error occurs during execution of the application logic. + */ + public static void main(String[] args) throws ExecutionException { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig(INFO); + // Optional - set the logger to write to a file + // Logger.setLoggerConfig(Logger.Level.INFO, file) + execAppLogic(); + } +} diff --git a/examples/kotlin/README.md b/examples/kotlin/README.md new file mode 100644 index 0000000000..a92035aeae --- /dev/null +++ b/examples/kotlin/README.md @@ -0,0 +1,22 @@ +## Run +Ensure that you have a server running on "localhost" on port "6379". To run the ClusterExample, make sure that the server has cluster mode enabled. If the server is running on a different host and/or port, update the StandaloneExample or ClusterExample with a configuration that matches your server settings. + +To run the Standalone example: +```shell +cd valkey-glide/examples/kotlin +./gradlew runStandalone +``` + +To run the Cluster example: +```shell +cd valkey-glide/examples/kotlin +./gradlew runCluster +``` + +## Version +These examples are running `valkey-glide` version `1.+`. In order to change the version, update the following section in the `build.gradle.kts` file: +```kotlin +dependencies { + implementation("io.valkey:valkey-glide:1.+:$classifier") +} +``` diff --git a/examples/kotlin/build.gradle.kts b/examples/kotlin/build.gradle.kts new file mode 100644 index 0000000000..37eee0a263 --- /dev/null +++ b/examples/kotlin/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + kotlin("jvm") version "1.9.23" + application + id("com.google.osdetector") version "1.7.3" +} + +group = "org.example" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +val os = osdetector.os +val classifier = osdetector.classifier +fun nettyTransport(): String { + if (os == "osx") + return "netty-transport-native-kqueue" + else if (os == "linux") + return "netty-transport-native-epoll" + else + throw Exception("Unsupported operating system $os") +} + +dependencies { + testImplementation(kotlin("test")) + implementation("io.valkey:valkey-glide:1.+:$classifier") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") +} + +tasks.test { + useJUnitPlatform() +} + +kotlin { + jvmToolchain(11) +} + +tasks.register("runStandalone") { + group = "application" + description = "Run the standalone example" + classpath = sourceSets.main.get().runtimeClasspath + mainClass = "glide.examples.StandaloneExample" +} + +tasks.register("runCluster") { + group = "application" + description = "Run the cluster example" + classpath = sourceSets.main.get().runtimeClasspath + mainClass = "glide.examples.ClusterExample" +} diff --git a/examples/kotlin/gradle.properties b/examples/kotlin/gradle.properties new file mode 100644 index 0000000000..7fc6f1ff27 --- /dev/null +++ b/examples/kotlin/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/examples/kotlin/gradle/wrapper/gradle-wrapper.jar b/examples/kotlin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..249e5832f0 Binary files /dev/null and b/examples/kotlin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/kotlin/gradle/wrapper/gradle-wrapper.properties b/examples/kotlin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..f9126bd412 --- /dev/null +++ b/examples/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Jul 09 17:54:28 PDT 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/kotlin/gradlew b/examples/kotlin/gradlew new file mode 100755 index 0000000000..1b6c787337 --- /dev/null +++ b/examples/kotlin/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/kotlin/gradlew.bat b/examples/kotlin/gradlew.bat new file mode 100644 index 0000000000..ac1b06f938 --- /dev/null +++ b/examples/kotlin/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/kotlin/settings.gradle.kts b/examples/kotlin/settings.gradle.kts new file mode 100644 index 0000000000..91009bbe9e --- /dev/null +++ b/examples/kotlin/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} +rootProject.name = "example" + diff --git a/examples/kotlin/src/main/kotlin/ClusterExample.kt b/examples/kotlin/src/main/kotlin/ClusterExample.kt new file mode 100644 index 0000000000..2a0d2c451c --- /dev/null +++ b/examples/kotlin/src/main/kotlin/ClusterExample.kt @@ -0,0 +1,142 @@ +package glide.examples + +import glide.api.GlideClusterClient +import glide.api.logging.Logger +import glide.api.models.commands.InfoOptions +import glide.api.models.configuration.GlideClusterClientConfiguration +import glide.api.models.configuration.NodeAddress +import glide.api.models.configuration.RequestRoutingConfiguration.SimpleMultiNodeRoute.ALL_NODES +import glide.api.models.exceptions.ClosingException +import glide.api.models.exceptions.ConnectionException +import glide.api.models.exceptions.ExecAbortException +import glide.api.models.exceptions.TimeoutException +import java.util.concurrent.CancellationException +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking + +object ClusterExample { + + /** + * Creates and returns a [GlideClusterClient] instance. + * + * This function initializes a [GlideClusterClient] with the provided list of nodes. + * The [nodesList] may contain the address of one or more cluster nodes, and the + * client will automatically discover all nodes in the cluster. + * + * @param nodesList A list of pairs where each pair + * contains a host (String) and port (Int). Defaults to [("localhost", 6379)]. + * + * @return An instance of [GlideClusterClient] connected to the discovered nodes. + */ + private suspend fun createClient(nodesList: List> = listOf(Pair("localhost", 6379))): GlideClusterClient { + // Check `GlideClusterClientConfiguration` for additional options. + val config = GlideClusterClientConfiguration.builder() + .addresses(nodesList.map({ (host: String, port: Int) -> NodeAddress.builder().host(host).port(port).build() })) + .clientName("test_cluster_client") + // Enable this field if the servers are configured with TLS. + //.useTLS(true) + .build() + + return GlideClusterClient.createClient(config).await() + } + + /** + * Executes the main logic of the application, performing basic operations + * such as SET, GET, PING, and INFO REPLICATION using the provided [GlideClusterClient]. + * + * @param client An instance of [GlideClusterClient]. + */ + private suspend fun appLogic(client: GlideClusterClient) { + // Send SET and GET + val setResponse = client.set("foo", "bar").await() + Logger.log(Logger.Level.INFO, "app", "Set response is $setResponse") + + val getResponse = client.get("foo").await() + Logger.log(Logger.Level.INFO, "app", "Get response is $getResponse") + + // Send PING to all primaries (according to Valkey's PING request_policy) + val pong = client.ping().await() + Logger.log(Logger.Level.INFO, "app", "Ping response is $pong") + + // Send INFO REPLICATION with routing option to all nodes + val infoReplResps = client.info( + InfoOptions.builder() + .section(InfoOptions.Section.REPLICATION) + .build(), + ALL_NODES + ).await() + Logger.log( + Logger.Level.INFO, + "app", + "INFO REPLICATION responses from all nodes are=\n$infoReplResps", + ) + } + + /** + * Executes the application logic with exception handling. + */ + private suspend fun execAppLogic() { + while (true) { + var client: GlideClusterClient? = null + try { + client = createClient() + return appLogic(client) + } catch (e: CancellationException) { + Logger.log(Logger.Level.ERROR, "glide", "Request cancelled: ${e.message}") + throw e + } catch (e: Exception) { + when (e) { + is ClosingException -> { + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + if (e.message?.contains("NOAUTH") == true) { + Logger.log(Logger.Level.ERROR, "glide", "Authentication error encountered: ${e.message}") + throw e + } else { + Logger.log(Logger.Level.WARN, "glide", "Client has closed and needs to be re-created: ${e.message}") + } + } + is TimeoutException -> { + // A request timed out. You may choose to retry the execution based on your application's logic + Logger.log(Logger.Level.ERROR, "glide", "Timeout encountered: ${e.message}") + throw e + } + is ConnectionException -> { + // The client wasn't able to reestablish the connection within the given retries + Logger.log(Logger.Level.ERROR, "glide", "Connection error encountered: ${e.message}") + throw e + } + else -> { + Logger.log(Logger.Level.ERROR, "glide", "Execution error encountered: ${e.cause}") + throw e + } + } + } finally { + try { + client?.close() + } catch (e: Exception) { + Logger.log( + Logger.Level.WARN, + "glide", + "Encountered an error while closing the client: ${e.cause}" + ) + } + } + } + } + + /** + * The entry point of the cluster example. This method sets up the logger configuration + * and executes the main application logic. + * + * @param args Command-line arguments passed to the application. + */ + @JvmStatic + fun main(args: Array) = runBlocking { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig(Logger.Level.INFO) + // Optional - set the logger to write to a file + // Logger.setLoggerConfig(Logger.Level.INFO, file) + execAppLogic() + } +} diff --git a/examples/kotlin/src/main/kotlin/StandaloneExample.kt b/examples/kotlin/src/main/kotlin/StandaloneExample.kt new file mode 100644 index 0000000000..7ca809944c --- /dev/null +++ b/examples/kotlin/src/main/kotlin/StandaloneExample.kt @@ -0,0 +1,127 @@ +package glide.examples + +import glide.api.GlideClient +import glide.api.logging.Logger +import glide.api.models.configuration.GlideClientConfiguration +import glide.api.models.configuration.NodeAddress +import glide.api.models.exceptions.ClosingException +import glide.api.models.exceptions.ConnectionException +import glide.api.models.exceptions.ExecAbortException +import glide.api.models.exceptions.TimeoutException +import java.util.concurrent.CancellationException +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking + +object StandaloneExample { + + /** + * Creates and returns a [GlideClient] instance. + * + * This function initializes a [GlideClient] with the provided list of nodes. + * The [nodesList] may contain either only primary node or a mix of primary + * and replica nodes. The [GlideClient] use these nodes to connect to + * the Standalone setup servers. + * + * @param nodesList A list of pairs where each pair + * contains a host (String) and port (Int). Defaults to [("localhost", 6379)]. + * + * @return An instance of [GlideClient] connected to the specified nodes. + */ + private suspend fun createClient(nodesList: List> = listOf(Pair("localhost", 6379))): GlideClient { + // Check `GlideClientConfiguration` for additional options. + val config = GlideClientConfiguration.builder() + .addresses(nodesList.map({ (host: String, port: Int) -> NodeAddress.builder().host(host).port(port).build() })) + // Enable this field if the servers are configured with TLS. + //.useTLS(true) + .build() + + return GlideClient.createClient(config).await() + } + + /** + * Executes the main logic of the application, performing basic operations such as SET, GET, and + * PING using the provided [GlideClient]. + * + * @param client An instance of [GlideClient]. + */ + private suspend fun appLogic(client: GlideClient) { + // Send SET and GET + val setResponse = client.set("foo", "bar").await() + Logger.log(Logger.Level.INFO, "app", "Set response is $setResponse") + + val getResponse = client.get("foo").await() + Logger.log(Logger.Level.INFO, "app", "Get response is $getResponse") + + // Send PING to the primary node + val pong = client.ping().await() + Logger.log(Logger.Level.INFO, "app", "Ping response is $pong") + } + + /** + * Executes the application logic with exception handling. + */ + private suspend fun execAppLogic() { + while (true) { + var client: GlideClient? = null + try { + client = createClient() + return appLogic(client) + } catch (e: CancellationException) { + Logger.log(Logger.Level.ERROR, "glide", "Request cancelled: ${e.message}") + throw e + } catch (e: Exception) { + when (e) { + is ClosingException -> { + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + if (e.message?.contains("NOAUTH") == true) { + Logger.log(Logger.Level.ERROR, "glide", "Authentication error encountered: ${e.message}") + throw e + } else { + Logger.log(Logger.Level.WARN, "glide", "Client has closed and needs to be re-created: ${e.message}") + } + } + is TimeoutException -> { + // A request timed out. You may choose to retry the execution based on your application's logic + Logger.log(Logger.Level.ERROR, "glide", "Timeout encountered: ${e.message}") + throw e + } + is ConnectionException -> { + // The client wasn't able to reestablish the connection within the given retries + Logger.log(Logger.Level.ERROR, "glide", "Connection error encountered: ${e.message}") + throw e + } + else -> { + Logger.log(Logger.Level.ERROR, "glide", "Execution error encountered: ${e.cause}") + throw e + } + } + } finally { + try { + client?.close() + } catch (e: Exception) { + Logger.log( + Logger.Level.WARN, + "glide", + "Encountered an error while closing the client: ${e.cause}" + ) + } + } + } + } + + /** + * The entry point of the standalone example. This method sets up the logger configuration + * and executes the main application logic. + * + * @param args Command-line arguments passed to the application. + */ + @JvmStatic + fun main(args: Array) = runBlocking { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig(Logger.Level.INFO) + // Optional - set the logger to write to a file + // Logger.setLoggerConfig(Logger.Level.INFO, file) + execAppLogic() + } +} diff --git a/examples/node/.gitignore b/examples/node/.gitignore deleted file mode 100644 index a6c7c2852d..0000000000 --- a/examples/node/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.js diff --git a/examples/node/index.ts b/examples/node/index.ts index 372b1ff845..49fa9a531d 100644 --- a/examples/node/index.ts +++ b/examples/node/index.ts @@ -12,7 +12,7 @@ async function sendPingToNode() { port: 6379, }, ]; - // Check `GlideClientConfiguration/ClusterClientConfiguration` for additional options. + // Check `GlideClientConfiguration/GlideClusterClientConfiguration` for additional options. const client = await GlideClient.createClient({ addresses: addresses, // if the server uses TLS, you'll need to enable it. Otherwise the connection attempt will time out silently. @@ -41,7 +41,7 @@ async function sendPingToRandomNodeInCluster() { port: 6380, }, ]; - // Check `GlideClientConfiguration/ClusterClientConfiguration` for additional options. + // Check `GlideClientConfiguration/GlideClusterClientConfiguration` for additional options. const client = await GlideClusterClient.createClient({ addresses: addresses, // if the cluster nodes use TLS, you'll need to enable it. Otherwise the connection attempt will time out silently. diff --git a/examples/scala/README.md b/examples/scala/README.md new file mode 100644 index 0000000000..fdbfa6b397 --- /dev/null +++ b/examples/scala/README.md @@ -0,0 +1,20 @@ +## Run +Ensure that you have a server running on "localhost" on port "6379". To run the ClusterExample, make sure that the server has cluster mode enabled. If the server is running on a different host and/or port, update the StandaloneExample or ClusterExample with a configuration that matches your server settings. + +To run the Standalone example: +```shell +cd valkey-glide/examples/scala +sbt "runMain StandaloneExample" +``` + +To run the Cluster example: +```shell +cd valkey-glide/examples/scala +sbt "runMain ClusterExample" +``` + +## Version +These examples are running `valkey-glide` version `1.+`. In order to change the version, update the following section in the `build.sbt` file: +```scala +libraryDependencies += "io.valkey" % "valkey-glide" % "1.+" classifier platformClassifier +``` diff --git a/examples/scala/build.sbt b/examples/scala/build.sbt new file mode 100644 index 0000000000..811d0e9840 --- /dev/null +++ b/examples/scala/build.sbt @@ -0,0 +1,23 @@ +ThisBuild / version := "0.1.0-SNAPSHOT" + +ThisBuild / scalaVersion := "3.3.3" + +lazy val root = (project in file(".")) + .settings( + name := "example" + ) + +// TODO: Get classifier using https://github.com/phdata/sbt-os-detector if/when https://repository.phdata.io/artifactory/libs-release works again +val os = System.getProperty("os.name").toLowerCase +val platformClassifier = { + (os, System.getProperty("os.arch").toLowerCase) match { + case (mac, arm) if mac.contains("mac") && (arm.contains("aarch") || arm.contains("arm")) => "osx-aarch_64" + case (mac, x86) if mac.contains("mac") && x86.contains("x86") => "osx-x86_64" + case (linux, arm) if linux.contains("linux") && (arm.contains("aarch") || arm.contains("arm")) => "linux-aarch_64" + case (linux, x86) if linux.contains("linux") && x86.contains("x86") => "linux-x86_64" + case (osName, archName) => throw new RuntimeException(s"Unsupported platform $osName $archName") + } +} + +libraryDependencies += "io.valkey" % "valkey-glide" % "1.+" classifier platformClassifier + diff --git a/examples/scala/project/build.properties b/examples/scala/project/build.properties new file mode 100644 index 0000000000..136f452e0d --- /dev/null +++ b/examples/scala/project/build.properties @@ -0,0 +1 @@ +sbt.version = 1.10.1 diff --git a/examples/scala/src/main/scala/ClusterExample.scala b/examples/scala/src/main/scala/ClusterExample.scala new file mode 100644 index 0000000000..b95b557f9c --- /dev/null +++ b/examples/scala/src/main/scala/ClusterExample.scala @@ -0,0 +1,138 @@ +import glide.api.GlideClusterClient +import glide.api.logging.Logger +import glide.api.models.commands.InfoOptions +import glide.api.models.configuration.{GlideClusterClientConfiguration, NodeAddress} +import glide.api.models.configuration.RequestRoutingConfiguration.SimpleMultiNodeRoute.ALL_NODES +import glide.api.models.exceptions.{ClosingException, ConnectionException, ExecAbortException, TimeoutException} + +import scala.concurrent.{Await, CancellationException, ExecutionContext, Future} +import scala.concurrent.duration.Duration +import scala.concurrent.ExecutionContext.Implicits.global +import scala.jdk.CollectionConverters.* +import scala.jdk.FutureConverters.* +import scala.util.{Failure, Try} + +object ClusterExample { + + /** + * Creates and returns a GlideClusterClient instance. + * + * This function initializes a GlideClusterClient with the provided list of nodes. + * The nodesList may contain the address of one or more cluster nodes, and the + * client will automatically discover all nodes in the cluster. + * + * @param nodesList A list of pairs where each pair + * contains a host (String) and port (Int). Defaults to [("localhost", 6379)]. + * + * @return An instance of Future[GlideClusterClient] connected to the specified nodes. + */ + private def createClient(nodesList: List[(String, Int)] = List(("localhost", 6379))): Future[GlideClusterClient] = { + // Check `GlideClientConfiguration` for additional options. + val config = GlideClusterClientConfiguration.builder() + .addresses(nodesList.map((host, port) => NodeAddress.builder().host(host).port(port).build()).asJava) + // Enable this field if the servers are configured with TLS. + //.useTLS(true) + .build() + // This cast is required in order to pass the config to createClient because the Scala type system + // is unable to resolve the Lombok builder result type. + .asInstanceOf[GlideClusterClientConfiguration] + + GlideClusterClient.createClient(config).asScala + } + + /** + * Executes the main logic of the application, performing basic operations + * such as SET, GET, PING, and INFO REPLICATION using the provided GlideClusterClient. + * + * @param client An instance of GlideClusterClient. + */ + private def appLogic(client: GlideClusterClient): Future[Unit] = { + for { + // Send SET and GET + setResponse <- client.set("foo", "bar").asScala + _ = Logger.log(Logger.Level.INFO, "app", s"Set response is $setResponse") + + getResponse <- client.get("foo").asScala + _ = Logger.log(Logger.Level.INFO, "app", s"Get response is $getResponse") + + // Send PING to all primaries (according to Valkey's PING request_policy) + pong <- client.ping().asScala + _ = Logger.log(Logger.Level.INFO, "app", s"Ping response is $pong") + + // Send INFO REPLICATION with routing option to all nodes + infoReplResps <- client.info( + InfoOptions.builder() + .section(InfoOptions.Section.REPLICATION) + .build(), + ALL_NODES + ).asScala + _ = Logger.log( + Logger.Level.INFO, + "app", + "INFO REPLICATION responses from all nodes are=\n$infoReplResps", + ) + } yield () + } + + /** + * Executes the application logic with exception handling. + */ + private def execAppLogic(): Future[Unit] = { + def loop(): Future[Unit] = { + createClient().flatMap(client => appLogic(client).andThen { + case _ => Try(client.close()) match { + case Failure(e) => + Logger.log( + Logger.Level.WARN, + "glide", + s"Encountered an error while closing the client: ${e.getCause}" + ) + case _ => () + } + }).recoverWith { + case e: CancellationException => + Logger.log(Logger.Level.ERROR, "glide", s"Request cancelled: ${e.getMessage}") + Future.failed(e) + case e: Exception => e.getCause match { + case e: ClosingException if e.getMessage.contains("NOAUTH") => + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + Logger.log(Logger.Level.ERROR, "glide", s"Authentication error encountered: ${e.getMessage}") + Future.failed(e) + case e: ClosingException => + Logger.log(Logger.Level.WARN, "glide", s"Client has closed and needs to be re-created: ${e.getMessage}") + loop() + case e: TimeoutException => + // A request timed out. You may choose to retry the execution based on your application's logic + Logger.log(Logger.Level.ERROR, "glide", s"Timeout encountered: ${e.getMessage}") + Future.failed(e) + case e: ConnectionException => + // The client wasn't able to reestablish the connection within the given retries + Logger.log(Logger.Level.ERROR, "glide", s"Connection error encountered: ${e.getMessage}") + Future.failed(e) + case _ => + Logger.log(Logger.Level.ERROR, "glide", s"Execution error encountered: ${e.getCause}") + Future.failed(e) + } + } + } + + loop() + } + + /** + * The entry point of the standalone example. This method sets up the logger configuration + * and executes the main application logic. + * + * @param args Command-line arguments passed to the application. + */ + def main(args: Array[String]): Unit = { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig(Logger.Level.INFO) + // Optional - set the logger to write to a file + // Logger.setLoggerConfig(Logger.Level.INFO, file) + + // Await is used only for this example. Not recommended for use in production environments. + Await.result(execAppLogic(), Duration.Inf) + } +} diff --git a/examples/scala/src/main/scala/StandaloneExample.scala b/examples/scala/src/main/scala/StandaloneExample.scala new file mode 100644 index 0000000000..401a4520c7 --- /dev/null +++ b/examples/scala/src/main/scala/StandaloneExample.scala @@ -0,0 +1,127 @@ +import glide.api.GlideClient +import glide.api.logging.Logger +import glide.api.models.configuration.GlideClientConfiguration +import glide.api.models.configuration.NodeAddress +import glide.api.models.exceptions.{ClosingException, ConnectionException, ExecAbortException, TimeoutException} + +import java.util.concurrent.TimeUnit +import scala.concurrent.{Await, CancellationException, ExecutionContext, Future} +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration.Duration +import scala.jdk.CollectionConverters.* +import scala.jdk.FutureConverters.* +import scala.util.{Failure, Try} + +object StandaloneExample { + + /** + * Creates and returns a GlideClient instance. + * + * This function initializes a GlideClient with the provided list of nodes. + * The nodesLis may contain either only primary node or a mix of primary + * and replica nodes. The GlideClient use these nodes to connect to + * the Standalone setup servers. + * + * @param nodesList A list of pairs where each pair + * contains a host (String) and port (Int). Defaults to [("localhost", 6379)]. + * + * @return An instance of Future[GlideClient] connected to the specified nodes. + */ + private def createClient(nodesList: List[(String, Int)] = List(("localhost", 6379))): Future[GlideClient] = { + // Check `GlideClientConfiguration` for additional options. + val config = GlideClientConfiguration.builder() + .addresses(nodesList.map((host, port) => NodeAddress.builder().host(host).port(port).build()).asJava) + // Enable this field if the servers are configured with TLS. + //.useTLS(true) + .build() + // This cast is required in order to pass the config to createClient because the Scala type system + // is unable to resolve the Lombok builder result type. + .asInstanceOf[GlideClientConfiguration] + + GlideClient.createClient(config).asScala + } + + /** + * Executes the main logic of the application, performing basic operations such as SET, GET, and + * PING using the provided GlideClient. + * + * @param client An instance of GlideClient. + */ + private def appLogic(client: GlideClient): Future[Unit] = { + for { + // Send SET and GET + setResponse <- client.set("foo", "bar").asScala + _ = Logger.log(Logger.Level.INFO, "app", s"Set response is $setResponse") + + getResponse <- client.get("foo").asScala + _ = Logger.log(Logger.Level.INFO, "app", s"Get response is $getResponse") + + // Send PING to the primary node + pong <- client.ping().asScala + _ = Logger.log(Logger.Level.INFO, "app", s"Ping response is $pong") + } yield () + } + + /** + * Executes the application logic with exception handling. + */ + private def execAppLogic(): Future[Unit] = { + def loop(): Future[Unit] = { + createClient().flatMap(client => appLogic(client).andThen { + case _ => Try(client.close()) match { + case Failure(e) => + Logger.log( + Logger.Level.WARN, + "glide", + s"Encountered an error while closing the client: ${e.getCause}" + ) + case _ => () + } + }).recoverWith { + case e: CancellationException => + Logger.log(Logger.Level.ERROR, "glide", s"Request cancelled: ${e.getMessage}") + Future.failed(e) + case e: Exception => e.getCause match { + case e: ClosingException if e.getMessage.contains("NOAUTH") => + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + Logger.log(Logger.Level.ERROR, "glide", s"Authentication error encountered: ${e.getMessage}") + Future.failed(e) + case e: ClosingException => + Logger.log(Logger.Level.WARN, "glide", s"Client has closed and needs to be re-created: ${e.getMessage}") + loop() + case e: TimeoutException => + // A request timed out. You may choose to retry the execution based on your application's logic + Logger.log(Logger.Level.ERROR, "glide", s"Timeout encountered: ${e.getMessage}") + Future.failed(e) + case e: ConnectionException => + // The client wasn't able to reestablish the connection within the given retries + Logger.log(Logger.Level.ERROR, "glide", s"Connection error encountered: ${e.getMessage}") + Future.failed(e) + case _ => + Logger.log(Logger.Level.ERROR, "glide", s"Execution error encountered: ${e.getCause}") + Future.failed(e) + } + } + } + + loop() + } + + /** + * The entry point of the standalone example. This method sets up the logger configuration + * and executes the main application logic. + * + * @param args Command-line arguments passed to the application. + */ + def main(args: Array[String]): Unit = { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig(Logger.Level.INFO) + // Optional - set the logger to write to a file + // Logger.setLoggerConfig(Logger.Level.INFO, file) + + val Timeout = 50 + // Change the timeout based on your production environments. + Await.result(execAppLogic(), Duration(Timeout, TimeUnit.SECONDS)) + } +} diff --git a/glide-core/Cargo.toml b/glide-core/Cargo.toml index 1d0b7d2967..d64009851d 100644 --- a/glide-core/Cargo.toml +++ b/glide-core/Cargo.toml @@ -30,6 +30,7 @@ nanoid = "0.4.0" [features] socket-layer = ["directories", "integer-encoding", "num_cpus", "protobuf", "tokio-util"] +standalone_heartbeat = [] [dev-dependencies] rsevents = "0.3.1" @@ -45,6 +46,8 @@ iai-callgrind = "0.9" tokio = { version = "1", features = ["rt-multi-thread"] } glide-core = { path = ".", features = ["socket-layer"] } # always enable this feature in tests. +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(standalone_heartbeat)'] } [build-dependencies] protobuf-codegen = "3" diff --git a/glide-core/THIRD_PARTY_LICENSES_RUST b/glide-core/THIRD_PARTY_LICENSES_RUST index 0bc2e35712..6902047fb8 100644 --- a/glide-core/THIRD_PARTY_LICENSES_RUST +++ b/glide-core/THIRD_PARTY_LICENSES_RUST @@ -683,10 +683,23 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ahash:0.8.11 +Package: adler2:2.0.0 The following copyrights and licenses were found in the source code of this package: +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + -- + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -912,7 +925,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: allocator-api2:0.2.18 +Package: ahash:0.8.11 The following copyrights and licenses were found in the source code of this package: @@ -1141,7 +1154,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: android-tzdata:0.1.1 +Package: allocator-api2:0.2.18 The following copyrights and licenses were found in the source code of this package: @@ -1370,7 +1383,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: android_system_properties:0.1.5 +Package: android-tzdata:0.1.1 The following copyrights and licenses were found in the source code of this package: @@ -1599,7 +1612,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: arc-swap:1.7.1 +Package: android_system_properties:0.1.5 The following copyrights and licenses were found in the source code of this package: @@ -1828,7 +1841,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: arcstr:1.2.0 +Package: arc-swap:1.7.1 The following copyrights and licenses were found in the source code of this package: @@ -2055,29 +2068,9 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. - -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. - ---- -Package: async-trait:0.1.81 +Package: arcstr:1.2.0 The following copyrights and licenses were found in the source code of this package: @@ -2304,9 +2297,29 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -- + +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + ---- -Package: backoff:0.4.0 +Package: async-trait:0.1.81 The following copyrights and licenses were found in the source code of this package: @@ -2535,7 +2548,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: backtrace:0.3.73 +Package: backoff:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -2764,7 +2777,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: base64:0.22.1 +Package: backtrace:0.3.73 The following copyrights and licenses were found in the source code of this package: @@ -2993,7 +3006,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bitflags:2.6.0 +Package: base64:0.22.1 The following copyrights and licenses were found in the source code of this package: @@ -3222,7 +3235,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bumpalo:3.16.0 +Package: bitflags:2.6.0 The following copyrights and licenses were found in the source code of this package: @@ -3451,32 +3464,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bytes:1.6.1 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: cfg-if:1.0.0 +Package: bumpalo:3.16.0 The following copyrights and licenses were found in the source code of this package: @@ -3705,7 +3693,84 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: chrono:0.4.38 +Package: byteorder:1.5.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -- + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +---- + +Package: bytes:1.7.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: cfg-if:1.0.0 The following copyrights and licenses were found in the source code of this package: @@ -3934,32 +3999,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: combine:4.6.7 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: core-foundation:0.9.4 +Package: chrono:0.4.38 The following copyrights and licenses were found in the source code of this package: @@ -4188,7 +4228,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: core-foundation-sys:0.8.6 +Package: combine:4.6.7 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: core-foundation:0.9.4 The following copyrights and licenses were found in the source code of this package: @@ -4417,42 +4482,17 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crc16:0.4.0 +Package: core-foundation-sys:0.8.7 The following copyrights and licenses were found in the source code of this package: -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION ----- - -Package: crc32fast:1.4.2 - -The following copyrights and licenses were found in the source code of this package: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. + 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. @@ -4671,7 +4711,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crossbeam-channel:0.5.13 +Package: crc16:0.4.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: crc32fast:1.4.2 The following copyrights and licenses were found in the source code of this package: @@ -4900,7 +4965,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crossbeam-utils:0.8.20 +Package: crossbeam-channel:0.5.13 The following copyrights and licenses were found in the source code of this package: @@ -5129,7 +5194,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: deranged:0.3.11 +Package: crossbeam-utils:0.8.20 The following copyrights and licenses were found in the source code of this package: @@ -5358,7 +5423,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: derivative:2.2.0 +Package: dashmap:6.0.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: deranged:0.3.11 The following copyrights and licenses were found in the source code of this package: @@ -5587,7 +5677,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: directories:4.0.1 +Package: derivative:2.2.0 The following copyrights and licenses were found in the source code of this package: @@ -5816,7 +5906,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: dirs-sys:0.3.7 +Package: directories:4.0.1 The following copyrights and licenses were found in the source code of this package: @@ -6045,7 +6135,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: dispose:0.5.0 +Package: dirs-sys:0.3.7 The following copyrights and licenses were found in the source code of this package: @@ -6274,7 +6364,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: dispose-derive:0.4.0 +Package: dispose:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -6503,7 +6593,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: fast-math:0.1.1 +Package: dispose-derive:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -6732,32 +6822,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: file-rotate:0.7.6 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: flate2:1.0.30 +Package: fast-math:0.1.1 The following copyrights and licenses were found in the source code of this package: @@ -6986,7 +7051,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: form_urlencoded:1.2.1 +Package: file-rotate:0.7.6 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: flate2:1.0.33 The following copyrights and licenses were found in the source code of this package: @@ -7215,7 +7305,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures:0.3.30 +Package: form_urlencoded:1.2.1 The following copyrights and licenses were found in the source code of this package: @@ -7444,7 +7534,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-channel:0.3.30 +Package: futures:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -7673,7 +7763,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-core:0.3.30 +Package: futures-channel:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -7902,7 +7992,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-executor:0.3.30 +Package: futures-core:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -8131,7 +8221,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-intrusive:0.5.0 +Package: futures-executor:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -8360,7 +8450,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-io:0.3.30 +Package: futures-intrusive:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -8589,7 +8679,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-macro:0.3.30 +Package: futures-io:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -8818,7 +8908,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-sink:0.3.30 +Package: futures-macro:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9047,7 +9137,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-task:0.3.30 +Package: futures-sink:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9276,7 +9366,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-util:0.3.30 +Package: futures-task:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9505,7 +9595,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: getrandom:0.2.15 +Package: futures-util:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9734,7 +9824,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: gimli:0.29.0 +Package: getrandom:0.2.15 The following copyrights and licenses were found in the source code of this package: @@ -9963,7 +10053,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: hashbrown:0.14.5 +Package: gimli:0.29.0 The following copyrights and licenses were found in the source code of this package: @@ -10192,7 +10282,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: heck:0.5.0 +Package: hashbrown:0.14.5 The following copyrights and licenses were found in the source code of this package: @@ -10421,7 +10511,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: hermit-abi:0.3.9 +Package: heck:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -10650,7 +10740,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: iana-time-zone:0.1.60 +Package: hermit-abi:0.3.9 The following copyrights and licenses were found in the source code of this package: @@ -10879,7 +10969,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: iana-time-zone-haiku:0.1.2 +Package: iana-time-zone:0.1.60 The following copyrights and licenses were found in the source code of this package: @@ -11108,7 +11198,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: idna:0.5.0 +Package: iana-time-zone-haiku:0.1.2 The following copyrights and licenses were found in the source code of this package: @@ -11337,7 +11427,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ieee754:0.2.6 +Package: idna:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -11566,63 +11656,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: instant:0.1.13 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: integer-encoding:4.0.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: itoa:1.0.11 +Package: ieee754:0.2.6 The following copyrights and licenses were found in the source code of this package: @@ -11851,23 +11885,79 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: js-sys:0.3.69 +Package: instant:0.1.13 The following copyrights and licenses were found in the source code of this package: - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. - 1. Definitions. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + +Package: integer-encoding:4.0.2 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: itoa:1.0.11 + +The following copyrights and licenses were found in the source code of this package: + + 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 @@ -12080,7 +12170,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: lazy_static:1.5.0 +Package: js-sys:0.3.70 The following copyrights and licenses were found in the source code of this package: @@ -12309,7 +12399,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: libc:0.2.155 +Package: lazy_static:1.5.0 The following copyrights and licenses were found in the source code of this package: @@ -12538,32 +12628,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: libredox:0.1.3 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: lock_api:0.4.12 +Package: libc:0.2.158 The following copyrights and licenses were found in the source code of this package: @@ -12792,7 +12857,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: log:0.4.22 +Package: libredox:0.1.3 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: lock_api:0.4.12 The following copyrights and licenses were found in the source code of this package: @@ -13021,7 +13111,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: logger_core:0.1.0 +Package: log:0.4.22 The following copyrights and licenses were found in the source code of this package: @@ -13227,11 +13317,7 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. ----- - -Package: memchr:2.7.4 - -The following copyrights and licenses were found in the source code of this package: + -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -13252,36 +13338,9 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to - ---- -Package: miniz_oxide:0.7.4 +Package: logger_core:0.1.0 The following copyrights and licenses were found in the source code of this package: @@ -13487,50 +13546,9 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -- - -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. - -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. - ---- -Package: mio:0.8.11 +Package: memchr:2.7.4 The following copyrights and licenses were found in the source code of this package: @@ -13553,59 +13571,36 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----- - -Package: nanoid:0.4.0 + -- -The following copyrights and licenses were found in the source code of this package: +This is free and unencumbered software released into the public domain. -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: nu-ansi-term:0.46.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +For more information, please refer to ---- -Package: num-bigint:0.4.6 +Package: miniz_oxide:0.7.4 The following copyrights and licenses were found in the source code of this package: @@ -13832,15 +13827,35 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----- + -- -Package: num-conv:0.1.0 +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. -The following copyrights and licenses were found in the source code of this package: +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + +---- + +Package: miniz_oxide:0.8.0 + +The following copyrights and licenses were found in the source code of this package: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -14053,6 +14068,51 @@ the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -- + +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + +---- + +Package: mio:1.0.2 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. @@ -14063,7 +14123,57 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: num-integer:0.1.46 +Package: nanoid:0.4.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: nu-ansi-term:0.46.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: num-bigint:0.4.6 The following copyrights and licenses were found in the source code of this package: @@ -14292,7 +14402,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: num-traits:0.2.19 +Package: num-conv:0.1.0 The following copyrights and licenses were found in the source code of this package: @@ -14521,7 +14631,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: num_cpus:1.16.0 +Package: num-integer:0.1.46 The following copyrights and licenses were found in the source code of this package: @@ -14750,7 +14860,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: object:0.36.1 +Package: num-traits:0.2.19 The following copyrights and licenses were found in the source code of this package: @@ -14979,7 +15089,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: once_cell:1.19.0 +Package: num_cpus:1.16.0 The following copyrights and licenses were found in the source code of this package: @@ -15208,7 +15318,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: openssl-probe:0.1.5 +Package: object:0.36.3 The following copyrights and licenses were found in the source code of this package: @@ -15437,32 +15547,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: overload:0.1.1 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: parking_lot:0.12.3 +Package: once_cell:1.19.0 The following copyrights and licenses were found in the source code of this package: @@ -15691,7 +15776,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: parking_lot_core:0.9.10 +Package: openssl-probe:0.1.5 The following copyrights and licenses were found in the source code of this package: @@ -15920,7 +16005,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: percent-encoding:2.3.1 +Package: overload:0.1.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: parking_lot:0.12.3 The following copyrights and licenses were found in the source code of this package: @@ -16149,7 +16259,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project:1.1.5 +Package: parking_lot_core:0.9.10 The following copyrights and licenses were found in the source code of this package: @@ -16378,7 +16488,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-internal:1.1.5 +Package: percent-encoding:2.3.1 The following copyrights and licenses were found in the source code of this package: @@ -16607,7 +16717,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-lite:0.2.14 +Package: pin-project:1.1.5 The following copyrights and licenses were found in the source code of this package: @@ -16836,7 +16946,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-utils:0.1.0 +Package: pin-project-internal:1.1.5 The following copyrights and licenses were found in the source code of this package: @@ -17065,7 +17175,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: powerfmt:0.2.0 +Package: pin-project-lite:0.2.14 The following copyrights and licenses were found in the source code of this package: @@ -17294,7 +17404,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ppv-lite86:0.2.17 +Package: pin-utils:0.1.0 The following copyrights and licenses were found in the source code of this package: @@ -17523,7 +17633,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro-error:1.0.4 +Package: powerfmt:0.2.0 The following copyrights and licenses were found in the source code of this package: @@ -17752,7 +17862,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro-error-attr:1.0.4 +Package: ppv-lite86:0.2.20 The following copyrights and licenses were found in the source code of this package: @@ -17981,7 +18091,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro2:1.0.86 +Package: proc-macro-error:1.0.4 The following copyrights and licenses were found in the source code of this package: @@ -18210,57 +18320,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: protobuf:3.5.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: protobuf-support:3.5.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: quote:1.0.36 +Package: proc-macro-error-attr:1.0.4 The following copyrights and licenses were found in the source code of this package: @@ -18489,7 +18549,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand:0.8.5 +Package: proc-macro2:1.0.86 The following copyrights and licenses were found in the source code of this package: @@ -18718,17 +18778,67 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand_chacha:0.3.1 +Package: protobuf:3.5.1 The following copyrights and licenses were found in the source code of this package: - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - 1. Definitions. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: protobuf-support:3.5.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: quote:1.0.37 + +The following copyrights and licenses were found in the source code of this package: + + 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. @@ -18947,7 +19057,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand_core:0.6.4 +Package: rand:0.8.5 The following copyrights and licenses were found in the source code of this package: @@ -19176,88 +19286,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: redis:0.25.2 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: redox_syscall:0.5.3 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: redox_users:0.4.5 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: rustc-demangle:0.1.24 +Package: rand_chacha:0.3.1 The following copyrights and licenses were found in the source code of this package: @@ -19486,7 +19515,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls:0.22.4 +Package: rand_core:0.6.4 The following copyrights and licenses were found in the source code of this package: @@ -19694,19 +19723,86 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - -- +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: redis:0.25.2 + +The following copyrights and licenses were found in the source code of this package: + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + +Package: redox_syscall:0.5.3 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: redox_users:0.4.6 + +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -19729,7 +19825,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-native-certs:0.7.1 +Package: rustc-demangle:0.1.24 The following copyrights and licenses were found in the source code of this package: @@ -19937,20 +20033,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - - -- - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -19972,7 +20054,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pemfile:2.1.2 +Package: rustls:0.22.4 The following copyrights and licenses were found in the source code of this package: @@ -20215,7 +20297,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pki-types:1.7.0 +Package: rustls-native-certs:0.7.2 The following copyrights and licenses were found in the source code of this package: @@ -20423,6 +20505,20 @@ The following copyrights and licenses were found in the source code of this pack -- +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + -- + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -20444,31 +20540,13 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-webpki:0.102.5 +Package: rustls-pemfile:2.1.3 The following copyrights and licenses were found in the source code of this package: -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - ----- - -Package: rustversion:1.0.17 - -The following copyrights and licenses were found in the source code of this package: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -20670,6 +20748,20 @@ The following copyrights and licenses were found in the source code of this pack -- +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + -- + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -20691,7 +20783,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ryu:1.0.18 +Package: rustls-pki-types:1.8.0 The following copyrights and licenses were found in the source code of this package: @@ -20899,36 +20991,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - ----- - -Package: schannel:0.1.23 - -The following copyrights and licenses were found in the source code of this package: - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -20950,7 +21012,25 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: scopeguard:1.2.0 +Package: rustls-webpki:0.102.6 + +The following copyrights and licenses were found in the source code of this package: + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +---- + +Package: rustversion:1.0.17 The following copyrights and licenses were found in the source code of this package: @@ -21179,7 +21259,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework:2.11.1 +Package: ryu:1.0.18 The following copyrights and licenses were found in the source code of this package: @@ -21387,6 +21467,36 @@ The following copyrights and licenses were found in the source code of this pack -- +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +---- + +Package: schannel:0.1.23 + +The following copyrights and licenses were found in the source code of this package: + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -21408,7 +21518,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework-sys:2.11.1 +Package: scopeguard:1.2.0 The following copyrights and licenses were found in the source code of this package: @@ -21637,7 +21747,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde:1.0.204 +Package: security-framework:2.11.1 The following copyrights and licenses were found in the source code of this package: @@ -21866,7 +21976,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde_derive:1.0.204 +Package: security-framework-sys:2.11.1 The following copyrights and licenses were found in the source code of this package: @@ -22095,88 +22205,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: sha1_smol:1.0.0 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: sharded-slab:0.1.7 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: slab:0.4.9 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: smallvec:1.13.2 +Package: serde:1.0.209 The following copyrights and licenses were found in the source code of this package: @@ -22405,7 +22434,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: socket2:0.5.7 +Package: serde_derive:1.0.209 The following copyrights and licenses were found in the source code of this package: @@ -22634,32 +22663,38 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: spin:0.9.8 +Package: sha1_smol:1.0.1 The following copyrights and licenses were found in the source code of this package: -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -Package: strum:0.26.3 +Package: sharded-slab:0.1.7 The following copyrights and licenses were found in the source code of this package: @@ -22684,7 +22719,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: strum_macros:0.26.4 +Package: slab:0.4.9 The following copyrights and licenses were found in the source code of this package: @@ -22709,48 +22744,17 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: subtle:2.6.1 +Package: smallvec:1.13.2 The following copyrights and licenses were found in the source code of this package: -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: syn:1.0.109 - -The following copyrights and licenses were found in the source code of this package: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. + 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. @@ -22969,7 +22973,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.71 +Package: socket2:0.5.7 The following copyrights and licenses were found in the source code of this package: @@ -23198,7 +23202,113 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: thiserror:1.0.62 +Package: spin:0.9.8 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: strum:0.26.3 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: strum_macros:0.26.4 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: subtle:2.6.1 + +The following copyrights and licenses were found in the source code of this package: + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + +Package: syn:1.0.109 The following copyrights and licenses were found in the source code of this package: @@ -23427,7 +23537,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: thiserror-impl:1.0.62 +Package: syn:2.0.76 The following copyrights and licenses were found in the source code of this package: @@ -23656,7 +23766,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: thread_local:1.1.8 +Package: thiserror:1.0.63 The following copyrights and licenses were found in the source code of this package: @@ -23885,7 +23995,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: time:0.3.36 +Package: thiserror-impl:1.0.63 The following copyrights and licenses were found in the source code of this package: @@ -24114,7 +24224,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: time-core:0.1.2 +Package: thread_local:1.1.8 The following copyrights and licenses were found in the source code of this package: @@ -24343,7 +24453,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: time-macros:0.2.18 +Package: time:0.3.36 The following copyrights and licenses were found in the source code of this package: @@ -24572,7 +24682,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: tinyvec:1.8.0 +Package: time-core:0.1.2 The following copyrights and licenses were found in the source code of this package: @@ -24799,29 +24909,9 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. - -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. - ---- -Package: tinyvec_macros:0.1.1 +Package: time-macros:0.2.18 The following copyrights and licenses were found in the source code of this package: @@ -25048,145 +25138,50 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. - -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. - ---- -Package: tokio:1.38.1 +Package: tinyvec:1.8.0 The following copyrights and licenses were found in the source code of this package: -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ ----- + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Package: tokio-macros:2.3.0 + 1. Definitions. -The following copyrights and licenses were found in the source code of this package: + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. + "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. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + "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. -Package: tokio-retry:0.3.0 + "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. -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tokio-rustls:0.25.0 - -The following copyrights and licenses were found in the source code of this package: - - 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). + "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 @@ -25372,184 +25367,29 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----- - -Package: tokio-util:0.7.11 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing:0.1.40 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-appender:0.2.3 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-attributes:0.1.27 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-core:0.1.32 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-log:0.2.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- + -- -Package: tracing-subscriber:0.3.18 +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. -The following copyrights and licenses were found in the source code of this package: +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +3. This notice may not be removed or altered from any source distribution. ---- -Package: unicode-bidi:0.3.15 +Package: tinyvec_macros:0.1.1 The following copyrights and licenses were found in the source code of this package: @@ -25776,1226 +25616,31 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----- - -Package: unicode-ident:1.0.12 + -- -The following copyrights and licenses were found in the source code of this package: +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -- - -UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE - -Unicode Data Files include all data files under the directories -http://www.unicode.org/Public/, http://www.unicode.org/reports/, -http://www.unicode.org/cldr/data/, http://source.icu- -project.org/repos/icu/, and -http://www.unicode.org/utility/trac/browser/. - -Unicode Data Files do not include PDF online code charts under the -directory http://www.unicode.org/Public/. - -Software includes any source code published in the Unicode Standard or -under the directories http://www.unicode.org/Public/, -http://www.unicode.org/reports/, http://www.unicode.org/cldr/data/, -http://source.icu-project.org/repos/icu/, and -http://www.unicode.org/utility/trac/browser/. - -NOTICE TO USER: Carefully read the following legal agreement. BY -DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA -FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY -ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF -THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, -DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. - -COPYRIGHT AND PERMISSION NOTICE - -Copyright © 1991-2016 Unicode, Inc. All rights reserved. Distributed -under the Terms of Use in http://www.unicode.org/copyright.html. - -Permission is hereby granted, free of charge, to any person obtaining a -copy of the Unicode data files and any associated documentation (the -"Data Files") or Unicode software and any associated documentation (the -"Software") to deal in the Data Files or Software without restriction, -including without limitation the rights to use, copy, modify, merge, -publish, distribute, and/or sell copies of the Data Files or Software, -and to permit persons to whom the Data Files or Software are furnished -to do so, provided that either - -(a) this copyright and permission notice appear with all copies of the -Data Files or Software, or - -(b) this copyright and permission notice appear in associated -Documentation. - -THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR -ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER -RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF -CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. - -Except as contained in this notice, the name of a copyright holder shall -not be used in advertising or otherwise to promote the sale, use or -other dealings in these Data Files or Software without prior written -authorization of the copyright holder. - ----- - -Package: unicode-normalization:0.1.23 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: untrusted:0.9.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - ----- - -Package: url:2.5.0 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: valuable:0.1.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: wasi:0.11.0+wasi-snapshot-preview1 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: - http://www.apache.org/licenses/LICENSE-2.0 +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. ---- LLVM Exceptions to the Apache 2.0 License ---- +3. This notice may not be removed or altered from any source distribution. -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. +---- -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. +Package: tokio:1.39.3 - -- +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -27009,222 +25654,43 @@ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: wasm-bindgen:0.2.92 - -The following copyrights and licenses were found in the source code of this package: - - 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. +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - END OF TERMS AND CONDITIONS +---- - APPENDIX: How to apply the Apache License to your work. +Package: tokio-macros:2.4.0 - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. +The following copyrights and licenses were found in the source code of this package: - Copyright [yyyy] [name of copyright owner] +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - http://www.apache.org/licenses/LICENSE-2.0 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +---- - -- +Package: tokio-retry:0.3.0 + +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -27247,7 +25713,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-backend:0.2.92 +Package: tokio-rustls:0.25.0 The following copyrights and licenses were found in the source code of this package: @@ -27476,213 +25942,159 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro:0.2.92 +Package: tokio-util:0.7.11 The following copyrights and licenses were found in the source code of this package: - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - 1. Definitions. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - "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. +Package: tracing:0.1.40 - "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. +The following copyrights and licenses were found in the source code of this package: - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - "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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - "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. +Package: tracing-appender:0.2.3 - "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." +The following copyrights and licenses were found in the source code of this package: - "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. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - 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. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - 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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - 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 +Package: tracing-attributes:0.1.27 - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and +The following copyrights and licenses were found in the source code of this package: - (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 +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - (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. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - 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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - 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. +Package: tracing-core:0.1.32 - 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. +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - 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. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - 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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - END OF TERMS AND CONDITIONS +---- - APPENDIX: How to apply the Apache License to your work. +Package: tracing-log:0.2.0 - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. +The following copyrights and licenses were found in the source code of this package: - Copyright [yyyy] [name of copyright owner] +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - http://www.apache.org/licenses/LICENSE-2.0 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +---- - -- +Package: tracing-subscriber:0.3.18 + +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -27705,7 +26117,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro-support:0.2.92 +Package: unicode-bidi:0.3.15 The following copyrights and licenses were found in the source code of this package: @@ -27934,7 +26346,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-shared:0.2.92 +Package: unicode-ident:1.0.12 The following copyrights and licenses were found in the source code of this package: @@ -28161,9 +26573,70 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -- + +UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE + +Unicode Data Files include all data files under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu- +project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +Unicode Data Files do not include PDF online code charts under the +directory http://www.unicode.org/Public/. + +Software includes any source code published in the Unicode Standard or +under the directories http://www.unicode.org/Public/, +http://www.unicode.org/reports/, http://www.unicode.org/cldr/data/, +http://source.icu-project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA +FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY +ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF +THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, +DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2016 Unicode, Inc. All rights reserved. Distributed +under the Terms of Use in http://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of the Unicode data files and any associated documentation (the +"Data Files") or Unicode software and any associated documentation (the +"Software") to deal in the Data Files or Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, and/or sell copies of the Data Files or Software, +and to permit persons to whom the Data Files or Software are furnished +to do so, provided that either + +(a) this copyright and permission notice appear with all copies of the +Data Files or Software, or + +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR +ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or +other dealings in these Data Files or Software without prior written +authorization of the copyright holder. + ---- -Package: winapi:0.3.9 +Package: unicode-normalization:0.1.23 The following copyrights and licenses were found in the source code of this package: @@ -28392,7 +26865,25 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: winapi-i686-pc-windows-gnu:0.4.0 +Package: untrusted:0.9.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +---- + +Package: url:2.5.0 The following copyrights and licenses were found in the source code of this package: @@ -28621,7 +27112,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: winapi-x86_64-pc-windows-gnu:0.4.0 +Package: valuable:0.1.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: wasi:0.11.0+wasi-snapshot-preview1 The following copyrights and licenses were found in the source code of this package: @@ -28829,31 +27345,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: windows-core:0.52.0 - -The following copyrights and licenses were found in the source code of this package: - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -29056,6 +27547,22 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + -- Permission is hereby granted, free of charge, to any person obtaining @@ -29079,7 +27586,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-sys:0.48.0 +Package: wasm-bindgen:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -29308,7 +27815,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-sys:0.52.0 +Package: wasm-bindgen-backend:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -29537,7 +28044,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.48.5 +Package: wasm-bindgen-macro:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -29766,7 +28273,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.52.6 +Package: wasm-bindgen-macro-support:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -29995,7 +28502,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.48.5 +Package: wasm-bindgen-shared:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -30224,7 +28731,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.52.6 +Package: winapi:0.3.9 The following copyrights and licenses were found in the source code of this package: @@ -30453,7 +28960,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.48.5 +Package: winapi-i686-pc-windows-gnu:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -30682,7 +29189,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.52.6 +Package: winapi-x86_64-pc-windows-gnu:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -30911,7 +29418,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.48.5 +Package: windows-core:0.52.0 The following copyrights and licenses were found in the source code of this package: @@ -31140,7 +29647,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.52.6 +Package: windows-sys:0.52.0 The following copyrights and licenses were found in the source code of this package: @@ -31369,7 +29876,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnullvm:0.52.6 +Package: windows-targets:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -31598,7 +30105,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.48.5 +Package: windows_aarch64_gnullvm:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -31827,7 +30334,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.52.6 +Package: windows_aarch64_msvc:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -32056,7 +30563,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.48.5 +Package: windows_i686_gnu:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -32285,7 +30792,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.52.6 +Package: windows_i686_gnullvm:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -32514,7 +31021,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnullvm:0.48.5 +Package: windows_i686_msvc:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -32743,7 +31250,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnullvm:0.52.6 +Package: windows_x86_64_gnu:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -32972,7 +31479,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_msvc:0.48.5 +Package: windows_x86_64_gnullvm:0.52.6 The following copyrights and licenses were found in the source code of this package: diff --git a/glide-core/src/client/mod.rs b/glide-core/src/client/mod.rs index 249a409041..4dde234f86 100644 --- a/glide-core/src/client/mod.rs +++ b/glide-core/src/client/mod.rs @@ -23,7 +23,7 @@ mod value_conversion; use tokio::sync::mpsc; pub const HEARTBEAT_SLEEP_DURATION: Duration = Duration::from_secs(1); - +pub const DEFAULT_RETRIES: u32 = 3; pub const DEFAULT_RESPONSE_TIMEOUT: Duration = Duration::from_millis(250); pub const DEFAULT_CONNECTION_ATTEMPT_TIMEOUT: Duration = Duration::from_millis(250); pub const DEFAULT_PERIODIC_CHECKS_INTERVAL: Duration = Duration::from_secs(60); @@ -450,7 +450,8 @@ async fn create_cluster_client( None => Some(DEFAULT_PERIODIC_CHECKS_INTERVAL), }; let mut builder = redis::cluster::ClusterClientBuilder::new(initial_nodes) - .connection_timeout(INTERNAL_CONNECTION_TIMEOUT); + .connection_timeout(INTERNAL_CONNECTION_TIMEOUT) + .retries(DEFAULT_RETRIES); if read_from_replicas { builder = builder.read_from_replicas(); } diff --git a/glide-core/src/client/reconnecting_connection.rs b/glide-core/src/client/reconnecting_connection.rs index c76da9cf42..4d962e40dd 100644 --- a/glide-core/src/client/reconnecting_connection.rs +++ b/glide-core/src/client/reconnecting_connection.rs @@ -165,7 +165,7 @@ impl ReconnectingConnection { create_connection(backend, connection_retry_strategy, push_sender).await } - fn node_address(&self) -> String { + pub(crate) fn node_address(&self) -> String { self.inner .backend .connection_info diff --git a/glide-core/src/client/standalone_client.rs b/glide-core/src/client/standalone_client.rs index 727ad906d9..fd17c538e8 100644 --- a/glide-core/src/client/standalone_client.rs +++ b/glide-core/src/client/standalone_client.rs @@ -6,7 +6,7 @@ use super::reconnecting_connection::ReconnectingConnection; use super::{ConnectionRequest, NodeAddress, TlsMode}; use crate::retry_strategies::RetryStrategy; use futures::{future, stream, StreamExt}; -#[cfg(standalone_heartbeat)] +#[cfg(feature = "standalone_heartbeat")] use logger_core::log_debug; use logger_core::log_warn; use rand::Rng; @@ -15,7 +15,7 @@ use redis::{PushInfo, RedisError, RedisResult, Value}; use std::sync::atomic::AtomicUsize; use std::sync::Arc; use tokio::sync::mpsc; -#[cfg(standalone_heartbeat)] +#[cfg(feature = "standalone_heartbeat")] use tokio::task; #[derive(Debug)] @@ -185,7 +185,7 @@ impl StandaloneClient { } let read_from = get_read_from(connection_request.read_from); - #[cfg(standalone_heartbeat)] + #[cfg(feature = "standalone_heartbeat")] for node in nodes.iter() { Self::start_heartbeat(node.clone()); } @@ -309,7 +309,25 @@ impl StandaloneClient { Some(ResponsePolicy::CombineArrays) => future::try_join_all(requests) .await .and_then(cluster_routing::combine_array_results), - Some(ResponsePolicy::Special) | None => { + Some(ResponsePolicy::CombineMaps) => future::try_join_all(requests) + .await + .and_then(cluster_routing::combine_map_results), + Some(ResponsePolicy::Special) => { + // Await all futures and collect results + let results = future::try_join_all(requests).await?; + // Create key-value pairs where the key is the node address and the value is the corresponding result + let node_result_pairs = self + .inner + .nodes + .iter() + .zip(results) + .map(|(node, result)| (Value::BulkString(node.node_address().into()), result)) + .collect(); + + Ok(Value::Map(node_result_pairs)) + } + + None => { // This is our assumption - if there's no coherent way to aggregate the responses, we just collect them in an array, and pass it to the user. // TODO - once Value::Error is merged, we can use join_all and report separate errors and also pass successes. future::try_join_all(requests).await.map(Value::Array) @@ -363,7 +381,7 @@ impl StandaloneClient { } } - #[cfg(standalone_heartbeat)] + #[cfg(feature = "standalone_heartbeat")] fn start_heartbeat(reconnecting_connection: ReconnectingConnection) { task::spawn(async move { loop { diff --git a/glide-core/src/client/value_conversion.rs b/glide-core/src/client/value_conversion.rs index 7a9ceced96..95021054c3 100644 --- a/glide-core/src/client/value_conversion.rs +++ b/glide-core/src/client/value_conversion.rs @@ -1265,6 +1265,10 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option { }) } } + b"PUBSUB NUMSUB" | b"PUBSUB SHARDNUMSUB" => Some(ExpectedReturnType::Map { + key_type: &None, + value_type: &None, + }), _ => None, } } diff --git a/glide-core/src/protobuf/command_request.proto b/glide-core/src/protobuf/command_request.proto index 96d3f7ae43..bd122f12a1 100644 --- a/glide-core/src/protobuf/command_request.proto +++ b/glide-core/src/protobuf/command_request.proto @@ -248,6 +248,11 @@ enum RequestType { Scan = 206; Wait = 208; XClaim = 209; + PubSubChannels = 210; + PubSubNumPat = 211; + PubSubNumSub = 212; + PubSubSChannels = 213; + PubSubSNumSub = 214; } message Command { diff --git a/glide-core/src/request_type.rs b/glide-core/src/request_type.rs index cd511f73a2..e574251e27 100644 --- a/glide-core/src/request_type.rs +++ b/glide-core/src/request_type.rs @@ -218,6 +218,11 @@ pub enum RequestType { Scan = 206, Wait = 208, XClaim = 209, + PubSubChannels = 210, + PubSubNumPat = 211, + PubSubNumSub = 212, + PubSubSChannels = 213, + PubSubSNumSub = 214, } fn get_two_word_command(first: &str, second: &str) -> Cmd { @@ -439,6 +444,11 @@ impl From<::protobuf::EnumOrUnknown> for RequestType { ProtobufRequestType::Wait => RequestType::Wait, ProtobufRequestType::XClaim => RequestType::XClaim, ProtobufRequestType::Scan => RequestType::Scan, + ProtobufRequestType::PubSubChannels => RequestType::PubSubChannels, + ProtobufRequestType::PubSubNumSub => RequestType::PubSubNumSub, + ProtobufRequestType::PubSubNumPat => RequestType::PubSubNumPat, + ProtobufRequestType::PubSubSChannels => RequestType::PubSubSChannels, + ProtobufRequestType::PubSubSNumSub => RequestType::PubSubSNumSub, } } } @@ -658,6 +668,11 @@ impl RequestType { RequestType::Wait => Some(cmd("WAIT")), RequestType::XClaim => Some(cmd("XCLAIM")), RequestType::Scan => Some(cmd("SCAN")), + RequestType::PubSubChannels => Some(get_two_word_command("PUBSUB", "CHANNELS")), + RequestType::PubSubNumSub => Some(get_two_word_command("PUBSUB", "NUMSUB")), + RequestType::PubSubNumPat => Some(get_two_word_command("PUBSUB", "NUMPAT")), + RequestType::PubSubSChannels => Some(get_two_word_command("PUBSUB", "SHARDCHANNELS")), + RequestType::PubSubSNumSub => Some(get_two_word_command("PUBSUB", "SHARDNUMSUB")), } } } diff --git a/glide-core/tests/test_standalone_client.rs b/glide-core/tests/test_standalone_client.rs index 75e3262f80..28d09f8b81 100644 --- a/glide-core/tests/test_standalone_client.rs +++ b/glide-core/tests/test_standalone_client.rs @@ -5,9 +5,8 @@ mod utilities; #[cfg(test)] mod standalone_client_tests { - use std::collections::HashMap; - use crate::utilities::mocks::{Mock, ServerMock}; + use std::collections::HashMap; use super::*; use glide_core::{ @@ -59,12 +58,12 @@ mod standalone_client_tests { #[rstest] #[serial_test::serial] #[timeout(LONG_STANDALONE_TEST_TIMEOUT)] - #[cfg(standalone_heartbeat)] + #[cfg(feature = "standalone_heartbeat")] fn test_detect_disconnect_and_reconnect_using_heartbeat(#[values(false, true)] use_tls: bool) { let (sender, receiver) = tokio::sync::oneshot::channel(); block_on_all(async move { let mut test_basics = setup_test_basics(use_tls).await; - let server = test_basics.server; + let server = test_basics.server.expect("Server shouldn't be None"); let address = server.get_client_addr(); drop(server); @@ -79,7 +78,7 @@ mod standalone_client_tests { let _new_server = receiver.await; tokio::time::sleep( - glide_core::client::HEARTBEAT_SLEEP_DURATION + Duration::from_secs(1), + glide_core::client::HEARTBEAT_SLEEP_DURATION + std::time::Duration::from_secs(1), ) .await; diff --git a/glide-core/tests/utilities/mod.rs b/glide-core/tests/utilities/mod.rs index 05c6f1f05a..55b2ae79f7 100644 --- a/glide-core/tests/utilities/mod.rs +++ b/glide-core/tests/utilities/mod.rs @@ -193,7 +193,7 @@ impl RedisServer { // prepare redis with TLS redis_cmd .arg("--tls-port") - .arg(&port.to_string()) + .arg(port.to_string()) .arg("--port") .arg("0") .arg("--tls-cert-file") diff --git a/go/.gitignore b/go/.gitignore index cbb173b891..93cec72cd1 100644 --- a/go/.gitignore +++ b/go/.gitignore @@ -5,3 +5,7 @@ reports # cbindgen generated header file lib.h + +# benchmarking results +benchmarks/results/** +benchmarks/gobenchmarks.json diff --git a/go/Cargo.toml b/go/Cargo.toml index 6d6c4ecb15..62872578da 100644 --- a/go/Cargo.toml +++ b/go/Cargo.toml @@ -13,6 +13,7 @@ redis = { path = "../submodules/redis-rs/redis", features = ["aio", "tokio-comp" glide-core = { path = "../glide-core", features = ["socket-layer"] } tokio = { version = "^1", features = ["rt", "macros", "rt-multi-thread", "time"] } protobuf = { version = "3.3.0", features = [] } +derivative = "2.2.0" [profile.release] lto = true diff --git a/go/DEVELOPER.md b/go/DEVELOPER.md index ab89b259b3..023828a0cf 100644 --- a/go/DEVELOPER.md +++ b/go/DEVELOPER.md @@ -1,12 +1,12 @@ # Developer Guide -This document describes how to set up your development environment to build and test the GLIDE for Redis Go wrapper. +This document describes how to set up your development environment to build and test the Valkey GLIDE Go wrapper. ### Development Overview We're excited to share that the GLIDE Go client is currently in development! However, it's important to note that this client is a work in progress and is not yet complete or fully tested. Your contributions and feedback are highly encouraged as we work towards refining and improving this implementation. Thank you for your interest and understanding as we continue to develop this Go wrapper. -The GLIDE for Redis Go wrapper consists of both Go and Rust code. The Go and Rust components communicate in two ways: +The Valkey GLIDE Go wrapper consists of both Go and Rust code. The Go and Rust components communicate in two ways: 1. Using the [protobuf](https://github.com/protocolbuffers/protobuf) protocol. 2. Using shared C objects. [cgo](https://pkg.go.dev/cmd/cgo) is used to interact with the C objects from Go code. @@ -25,11 +25,10 @@ Software Dependencies - openssl - openssl-dev - rustup -- redis -**Redis installation** +**Valkey installation** -To install redis-server and redis-cli on your host, follow the [Redis Installation Guide](https://redis.io/docs/install/install-redis/). +To install valkey-server and valkey-cli on your host, follow the [Valkey Installation Guide](https://github.com/valkey-io/valkey). **Dependencies installation for Ubuntu** @@ -104,7 +103,7 @@ Before starting this step, make sure you've installed all software requirements. ```bash VERSION=0.1.0 # You can modify this to other released version or set it to "main" to get the unstable branch git clone --branch ${VERSION} https://github.com/valkey-io/valkey-glide.git - cd glide-for-redis + cd valkey-glide ``` 2. Initialize git submodule: ```bash @@ -115,20 +114,28 @@ Before starting this step, make sure you've installed all software requirements. cd go make install-build-tools ``` -4. Build the Go wrapper: +4. If on CentOS or Ubuntu, add the glide-rs library to LD_LIBRARY_PATH: + ```bash + # Replace "" with the path to the valkey-glide root, eg "$HOME/Projects/valkey-glide" + GLIDE_ROOT_FOLDER_PATH= + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GLIDE_ROOT_FOLDER_PATH/go/target/release/deps/ + ``` +5. Build the Go wrapper: ```bash make build ``` -5. Run tests: - 1. Ensure that you have installed redis-server and redis-cli on your host. You can find the Redis installation guide at the following link: [Redis Installation Guide](https://redis.io/docs/install/install-redis/install-redis-on-linux/). +6. Run tests: + 1. Ensure that you have installed valkey-server and valkey-cli on your host. You can find the Valkey installation guide at the following link: [Valkey Installation Guide](https://github.com/valkey-io/valkey). 2. Execute the following command from the go folder: ```bash go test -race ./... ``` -6. Install Go development tools with: - +7. Install Go development tools with: ```bash + # For go1.22: make install-dev-tools + # For go1.18: + make install-dev-tools-go1.18 ``` ### Test @@ -192,7 +199,11 @@ Run from the main `/go` folder 1. Go ```bash + # For go1.22: make install-dev-tools + # For go1.18: + make install-dev-tools-go1.18 + make lint ``` 2. Rust @@ -212,6 +223,18 @@ Run from the main `/go` folder make format ``` +### Benchmarks + +To run the benchmarks, ensure you have followed the [build and installation steps](#building-and-installation-steps) (the tests do not have to be run). Then execute the following: + +```bash +cd go/benchmarks +# To see a list of available options and their defaults: +go run . -help +# An example command setting various options: +go run . -resultsFile gobenchmarks.json -dataSize "100 1000" -concurrentTasks "10 100" -clients all -host localhost -port 6379 -clientCount "1 5" -tls +``` + ### Recommended extensions for VS Code - [Go](https://marketplace.visualstudio.com/items?itemName=golang.Go) diff --git a/go/Makefile b/go/Makefile index a628643d1c..b15d198771 100644 --- a/go/Makefile +++ b/go/Makefile @@ -1,19 +1,22 @@ install-build-tools: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.33.0 + cargo install cbindgen -install-dev-tools-go1.18.10: +install-dev-tools-go1.18: go install github.com/vakenbolt/go-test-report@v0.9.3 go install mvdan.cc/gofumpt@v0.4.0 go install github.com/segmentio/golines@v0.11.0 go install honnef.co/go/tools/cmd/staticcheck@v0.3.3 - cargo install cbindgen -install-dev-tools-go1.22.0: +install-dev-tools-go1.18.10: install-dev-tools-go1.18 + +install-dev-tools-go1.22: go install github.com/vakenbolt/go-test-report@v0.9.3 go install mvdan.cc/gofumpt@v0.6.0 go install github.com/segmentio/golines@v0.12.2 go install honnef.co/go/tools/cmd/staticcheck@v0.4.6 - cargo install cbindgen + +install-dev-tools-go1.22.0: install-dev-tools-go1.22 install-dev-tools: install-dev-tools-go1.22.0 @@ -25,11 +28,26 @@ install-tools: install-tools-go1.22.0 build: build-glide-client generate-protobuf go build ./... + cd benchmarks && go build -ldflags="-w" ./... + +build-debug: build-glide-client-debug generate-protobuf + go build -gcflags "-l -N" ./... + cd benchmarks && go build -gcflags "-l -N" ./... + +clean: + go clean + rm -f lib.h + rm -f benchmarks/benchmarks + build-glide-client: cargo build --release cbindgen --config cbindgen.toml --crate glide-rs --output lib.h +build-glide-client-debug: + cargo build + cbindgen --config cbindgen.toml --crate glide-rs --output lib.h + generate-protobuf: mkdir -p protobuf protoc --proto_path=../glide-core/src/protobuf \ @@ -55,8 +73,12 @@ format: golines -w --shorten-comments -m 127 . test: + LD_LIBRARY_PATH=$(shell find . -name libglide_rs.so|grep -w release|tail -1|xargs dirname|xargs readlink -f):${LD_LIBRARY_PATH} \ go test -v -race ./... +# Note: this task is no longer run by CI because: +# - build failures that occur while running the task can be hidden by the task; CI still reports success in these scenarios. +# - there is not a good way to both generate a test report and log the test outcomes to GH actions. test-and-report: mkdir -p reports go test -v -race ./... -json | go-test-report -o reports/test-report.html diff --git a/go/api/base_client.go b/go/api/base_client.go new file mode 100644 index 0000000000..50938612aa --- /dev/null +++ b/go/api/base_client.go @@ -0,0 +1,164 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package api + +// #cgo LDFLAGS: -L../target/release -lglide_rs +// #include "../lib.h" +// +// void successCallback(void *channelPtr, struct CommandResponse *message); +// void failureCallback(void *channelPtr, char *errMessage, RequestErrorType errType); +import "C" + +import ( + "unsafe" + + "github.com/valkey-io/valkey-glide/go/glide/protobuf" + "google.golang.org/protobuf/proto" +) + +// BaseClient defines an interface for methods common to both [GlideClient] and [GlideClusterClient]. +type BaseClient interface { + StringCommands + + // Close terminates the client by closing all associated resources. + Close() +} + +const OK = "OK" + +type payload struct { + value *C.struct_CommandResponse + error error +} + +//export successCallback +func successCallback(channelPtr unsafe.Pointer, cResponse *C.struct_CommandResponse) { + response := cResponse + resultChannel := *(*chan payload)(channelPtr) + resultChannel <- payload{value: response, error: nil} +} + +//export failureCallback +func failureCallback(channelPtr unsafe.Pointer, cErrorMessage *C.char, cErrorType C.RequestErrorType) { + resultChannel := *(*chan payload)(channelPtr) + resultChannel <- payload{value: nil, error: goError(cErrorType, cErrorMessage)} +} + +type clientConfiguration interface { + toProtobuf() *protobuf.ConnectionRequest +} + +type baseClient struct { + coreClient unsafe.Pointer +} + +// Creates a connection by invoking the `create_client` function from Rust library via FFI. +// Passes the pointers to callback functions which will be invoked when the command succeeds or fails. +// Once the connection is established, this function invokes `free_connection_response` exposed by rust library to free the +// connection_response to avoid any memory leaks. +func createClient(config clientConfiguration) (*baseClient, error) { + request := config.toProtobuf() + msg, err := proto.Marshal(request) + if err != nil { + return nil, err + } + + byteCount := len(msg) + requestBytes := C.CBytes(msg) + cResponse := (*C.struct_ConnectionResponse)( + C.create_client( + (*C.uchar)(requestBytes), + C.uintptr_t(byteCount), + (C.SuccessCallback)(unsafe.Pointer(C.successCallback)), + (C.FailureCallback)(unsafe.Pointer(C.failureCallback)), + ), + ) + defer C.free_connection_response(cResponse) + + cErr := cResponse.connection_error_message + if cErr != nil { + message := C.GoString(cErr) + return nil, &ConnectionError{message} + } + + return &baseClient{cResponse.conn_ptr}, nil +} + +// Close terminates the client by closing all associated resources. +func (client *baseClient) Close() { + if client.coreClient == nil { + return + } + + C.close_client(client.coreClient) + client.coreClient = nil +} + +func (client *baseClient) executeCommand(requestType C.RequestType, args []string) (*C.struct_CommandResponse, error) { + if client.coreClient == nil { + return nil, &ClosingError{"ExecuteCommand failed. The client is closed."} + } + + cArgs, argLengths := toCStrings(args) + + resultChannel := make(chan payload) + resultChannelPtr := uintptr(unsafe.Pointer(&resultChannel)) + + C.command( + client.coreClient, + C.uintptr_t(resultChannelPtr), + uint32(requestType), + C.size_t(len(args)), + &cArgs[0], + &argLengths[0], + ) + payload := <-resultChannel + if payload.error != nil { + return nil, payload.error + } + return payload.value, nil +} + +// Convert `s` of type `string` into `[]byte` +func StringToBytes(s string) []byte { + p := unsafe.StringData(s) + b := unsafe.Slice(p, len(s)) + return b +} + +// Zero copying conversion from go's []string into C pointers +func toCStrings(args []string) ([]C.uintptr_t, []C.ulong) { + cStrings := make([]C.uintptr_t, len(args)) + stringLengths := make([]C.ulong, len(args)) + for i, str := range args { + bytes := StringToBytes(str) + ptr := uintptr(unsafe.Pointer(&bytes[0])) + cStrings[i] = C.uintptr_t(ptr) + stringLengths[i] = C.size_t(len(str)) + } + return cStrings, stringLengths +} + +func (client *baseClient) Set(key string, value string) (string, error) { + result, err := client.executeCommand(C.Set, []string{key, value}) + if err != nil { + return "", err + } + return handleStringResponse(result), nil +} + +func (client *baseClient) SetWithOptions(key string, value string, options *SetOptions) (string, error) { + result, err := client.executeCommand(C.Set, append([]string{key, value}, options.toArgs()...)) + if err != nil { + return "", err + } + return handleStringOrNullResponse(result), nil +} + +func (client *baseClient) Get(key string) (string, error) { + result, err := client.executeCommand(C.Get, []string{key}) + if err != nil { + return "", err + } + return handleStringOrNullResponse(result), nil +} diff --git a/go/api/command_options.go b/go/api/command_options.go new file mode 100644 index 0000000000..d5bc66498d --- /dev/null +++ b/go/api/command_options.go @@ -0,0 +1,71 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package api + +import "strconv" + +// SetOptions represents optional arguments for the [api.StringCommands.SetWithOptions] command. +// +// See [valkey.io] +// +// [valkey.io]: https://valkey.io/commands/set/ +type SetOptions struct { + // If ConditionalSet is not set the value will be set regardless of prior value existence. If value isn't set because of + // the condition, [api.StringCommands.SetWithOptions] will return a zero-value string (""). + ConditionalSet ConditionalSet + // Set command to return the old value stored at the given key, or a zero-value string ("") if the key did not exist. An + // error is returned and [api.StringCommands.SetWithOptions] is aborted if the value stored at key is not a string. + // Equivalent to GET in the valkey API. + ReturnOldValue bool + // If not set, no expiry time will be set for the value. + Expiry *Expiry +} + +func (opts *SetOptions) toArgs() []string { + args := []string{} + if opts.ConditionalSet != "" { + args = append(args, string(opts.ConditionalSet)) + } + + if opts.ReturnOldValue { + args = append(args, returnOldValue) + } + + if opts.Expiry != nil { + args = append(args, string(opts.Expiry.Type)) + if opts.Expiry.Type != KeepExisting { + args = append(args, strconv.FormatUint(opts.Expiry.Count, 10)) + } + } + + return args +} + +const returnOldValue = "GET" + +// A ConditionalSet defines whether a new value should be set or not. +type ConditionalSet string + +const ( + // OnlyIfExists only sets the key if it already exists. Equivalent to "XX" in the valkey API. + OnlyIfExists ConditionalSet = "XX" + // OnlyIfDoesNotExist only sets the key if it does not already exist. Equivalent to "NX" in the valkey API. + OnlyIfDoesNotExist ConditionalSet = "NX" +) + +// Expiry is used to configure the lifetime of a value. +type Expiry struct { + Type ExpiryType + Count uint64 +} + +// An ExpiryType is used to configure the type of expiration for a value. +type ExpiryType string + +const ( + KeepExisting ExpiryType = "KEEPTTL" // keep the existing expiration of the value + Seconds ExpiryType = "EX" // expire the value after [api.Expiry.Count] seconds + Milliseconds ExpiryType = "PX" // expire the value after [api.Expiry.Count] milliseconds + UnixSeconds ExpiryType = "EXAT" // expire the value after the Unix time specified by [api.Expiry.Count], in seconds + UnixMilliseconds ExpiryType = "PXAT" // expire the value after the Unix time specified by [api.Expiry.Count], in milliseconds +) diff --git a/go/api/commands.go b/go/api/commands.go new file mode 100644 index 0000000000..c19f318df1 --- /dev/null +++ b/go/api/commands.go @@ -0,0 +1,52 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package api + +// StringCommands defines an interface for the "String Commands" group of commands for standalone and cluster clients. +// +// See [valkey.io] for details. +// +// [valkey.io]: https://valkey.io/commands/?group=string +type StringCommands interface { + // Set the given key with the given value. The return value is a response from Valkey containing the string "OK". + // + // See [valkey.io] for details. + // + // For example: + // + // result, err := client.Set("key", "value") + // + // [valkey.io]: https://valkey.io/commands/set/ + Set(key string, value string) (string, error) + + // SetWithOptions sets the given key with the given value using the given options. The return value is dependent on the + // passed options. If the value is successfully set, "OK" is returned. If value isn't set because of [OnlyIfExists] or + // [OnlyIfDoesNotExist] conditions, an empty string is returned (""). If [SetOptions#ReturnOldValue] is set, the old + // value is returned. + // + // See [valkey.io] for details. + // + // For example: + // + // result, err := client.SetWithOptions("key", "value", &api.SetOptions{ + // ConditionalSet: api.OnlyIfExists, + // Expiry: &api.Expiry{ + // Type: api.Seconds, + // Count: uint64(5), + // }, + // }) + // + // [valkey.io]: https://valkey.io/commands/set/ + SetWithOptions(key string, value string, options *SetOptions) (string, error) + + // Get string value associated with the given key, or an empty string is returned ("") if no such value exists + // + // See [valkey.io] for details. + // + // For example: + // + // result, err := client.Get("key") + // + // [valkey.io]: https://valkey.io/commands/get/ + Get(key string) (string, error) +} diff --git a/go/api/config.go b/go/api/config.go index cc20d5e7c8..76b3c14fbf 100644 --- a/go/api/config.go +++ b/go/api/config.go @@ -27,27 +27,27 @@ func (addr *NodeAddress) toProtobuf() *protobuf.NodeAddress { return &protobuf.NodeAddress{Host: addr.Host, Port: uint32(addr.Port)} } -// RedisCredentials represents the credentials for connecting to a Redis server. -type RedisCredentials struct { - // The username that will be used for authenticating connections to the Redis servers. If not supplied, "default" +// ServerCredentials represents the credentials for connecting to servers. +type ServerCredentials struct { + // The username that will be used for authenticating connections to the servers. If not supplied, "default" // will be used. username string - // The password that will be used for authenticating connections to the Redis servers. + // The password that will be used for authenticating connections to the servers. password string } -// NewRedisCredentials returns a [RedisCredentials] struct with the given username and password. -func NewRedisCredentials(username string, password string) *RedisCredentials { - return &RedisCredentials{username, password} +// NewServerCredentials returns a [ServerCredentials] struct with the given username and password. +func NewServerCredentials(username string, password string) *ServerCredentials { + return &ServerCredentials{username, password} } -// NewRedisCredentialsWithDefaultUsername returns a [RedisCredentials] struct with a default username of "default" and the +// NewServerCredentialsWithDefaultUsername returns a [ServerCredentials] struct with a default username of "default" and the // given password. -func NewRedisCredentialsWithDefaultUsername(password string) *RedisCredentials { - return &RedisCredentials{password: password} +func NewServerCredentialsWithDefaultUsername(password string) *ServerCredentials { + return &ServerCredentials{password: password} } -func (creds *RedisCredentials) toProtobuf() *protobuf.AuthenticationInfo { +func (creds *ServerCredentials) toProtobuf() *protobuf.AuthenticationInfo { return &protobuf.AuthenticationInfo{Username: creds.username, Password: creds.password} } @@ -73,7 +73,7 @@ func mapReadFrom(readFrom ReadFrom) protobuf.ReadFrom { type baseClientConfiguration struct { addresses []NodeAddress useTLS bool - credentials *RedisCredentials + credentials *ServerCredentials readFrom ReadFrom requestTimeout int clientName string @@ -140,21 +140,20 @@ func (strategy *BackoffStrategy) toProtobuf() *protobuf.ConnectionRetryStrategy } } -// RedisClientConfiguration represents the configuration settings for a Standalone Redis client. baseClientConfiguration is an -// embedded struct that contains shared settings for standalone and cluster clients. -type RedisClientConfiguration struct { +// GlideClientConfiguration represents the configuration settings for a Standalone client. +type GlideClientConfiguration struct { baseClientConfiguration reconnectStrategy *BackoffStrategy databaseId int } -// NewRedisClientConfiguration returns a [RedisClientConfiguration] with default configuration settings. For further -// configuration, use the [RedisClientConfiguration] With* methods. -func NewRedisClientConfiguration() *RedisClientConfiguration { - return &RedisClientConfiguration{} +// NewGlideClientConfiguration returns a [GlideClientConfiguration] with default configuration settings. For further +// configuration, use the [GlideClientConfiguration] With* methods. +func NewGlideClientConfiguration() *GlideClientConfiguration { + return &GlideClientConfiguration{} } -func (config *RedisClientConfiguration) toProtobuf() *protobuf.ConnectionRequest { +func (config *GlideClientConfiguration) toProtobuf() *protobuf.ConnectionRequest { request := config.baseClientConfiguration.toProtobuf() request.ClusterModeEnabled = false if config.reconnectStrategy != nil { @@ -171,14 +170,16 @@ func (config *RedisClientConfiguration) toProtobuf() *protobuf.ConnectionRequest // WithAddress adds an address for a known node in the cluster to this configuration's list of addresses. WithAddress can be // called multiple times to add multiple addresses to the list. If the server is in cluster mode the list can be partial, as // the client will attempt to map out the cluster and find all nodes. If the server is in standalone mode, only nodes whose -// addresses were provided will be used by the client. For example: +// addresses were provided will be used by the client. // -// config := NewRedisClientConfiguration(). +// For example: +// +// config := NewGlideClientConfiguration(). // WithAddress(&NodeAddress{ // Host: "sample-address-0001.use1.cache.amazonaws.com", Port: 6379}). // WithAddress(&NodeAddress{ // Host: "sample-address-0002.use1.cache.amazonaws.com", Port: 6379}) -func (config *RedisClientConfiguration) WithAddress(address *NodeAddress) *RedisClientConfiguration { +func (config *GlideClientConfiguration) WithAddress(address *NodeAddress) *GlideClientConfiguration { config.addresses = append(config.addresses, *address) return config } @@ -186,20 +187,20 @@ func (config *RedisClientConfiguration) WithAddress(address *NodeAddress) *Redis // WithUseTLS configures the TLS settings for this configuration. Set to true if communication with the cluster should use // Transport Level Security. This setting should match the TLS configuration of the server/cluster, otherwise the connection // attempt will fail. -func (config *RedisClientConfiguration) WithUseTLS(useTLS bool) *RedisClientConfiguration { +func (config *GlideClientConfiguration) WithUseTLS(useTLS bool) *GlideClientConfiguration { config.useTLS = useTLS return config } // WithCredentials sets the credentials for the authentication process. If none are set, the client will not authenticate // itself with the server. -func (config *RedisClientConfiguration) WithCredentials(credentials *RedisCredentials) *RedisClientConfiguration { +func (config *GlideClientConfiguration) WithCredentials(credentials *ServerCredentials) *GlideClientConfiguration { config.credentials = credentials return config } // WithReadFrom sets the client's [ReadFrom] strategy. If not set, [Primary] will be used. -func (config *RedisClientConfiguration) WithReadFrom(readFrom ReadFrom) *RedisClientConfiguration { +func (config *GlideClientConfiguration) WithReadFrom(readFrom ReadFrom) *GlideClientConfiguration { config.readFrom = readFrom return config } @@ -208,47 +209,47 @@ func (config *RedisClientConfiguration) WithReadFrom(readFrom ReadFrom) *RedisCl // encompasses sending the request, awaiting for a response from the server, and any required reconnections or retries. If the // specified timeout is exceeded for a pending request, it will result in a timeout error. If not set, a default value will be // used. -func (config *RedisClientConfiguration) WithRequestTimeout(requestTimeout int) *RedisClientConfiguration { +func (config *GlideClientConfiguration) WithRequestTimeout(requestTimeout int) *GlideClientConfiguration { config.requestTimeout = requestTimeout return config } // WithClientName sets the client name to be used for the client. Will be used with CLIENT SETNAME command during connection // establishment. -func (config *RedisClientConfiguration) WithClientName(clientName string) *RedisClientConfiguration { +func (config *GlideClientConfiguration) WithClientName(clientName string) *GlideClientConfiguration { config.clientName = clientName return config } // WithReconnectStrategy sets the [BackoffStrategy] used to determine how and when to reconnect, in case of connection // failures. If not set, a default backoff strategy will be used. -func (config *RedisClientConfiguration) WithReconnectStrategy(strategy *BackoffStrategy) *RedisClientConfiguration { +func (config *GlideClientConfiguration) WithReconnectStrategy(strategy *BackoffStrategy) *GlideClientConfiguration { config.reconnectStrategy = strategy return config } // WithDatabaseId sets the index of the logical database to connect to. -func (config *RedisClientConfiguration) WithDatabaseId(id int) *RedisClientConfiguration { +func (config *GlideClientConfiguration) WithDatabaseId(id int) *GlideClientConfiguration { config.databaseId = id return config } -// RedisClusterClientConfiguration represents the configuration settings for a Cluster Redis client. +// GlideClusterClientConfiguration represents the configuration settings for a Cluster Glide client. // Note: Currently, the reconnection strategy in cluster mode is not configurable, and exponential backoff with fixed values is // used. -type RedisClusterClientConfiguration struct { +type GlideClusterClientConfiguration struct { baseClientConfiguration } -// NewRedisClusterClientConfiguration returns a [RedisClusterClientConfiguration] with default configuration settings. For -// further configuration, use the [RedisClientConfiguration] With* methods. -func NewRedisClusterClientConfiguration() *RedisClusterClientConfiguration { - return &RedisClusterClientConfiguration{ +// NewGlideClusterClientConfiguration returns a [GlideClusterClientConfiguration] with default configuration settings. For +// further configuration, use the [GlideClientConfiguration] With* methods. +func NewGlideClusterClientConfiguration() *GlideClusterClientConfiguration { + return &GlideClusterClientConfiguration{ baseClientConfiguration: baseClientConfiguration{}, } } -func (config *RedisClusterClientConfiguration) toProtobuf() *protobuf.ConnectionRequest { +func (config *GlideClusterClientConfiguration) toProtobuf() *protobuf.ConnectionRequest { request := config.baseClientConfiguration.toProtobuf() request.ClusterModeEnabled = true return request @@ -257,14 +258,16 @@ func (config *RedisClusterClientConfiguration) toProtobuf() *protobuf.Connection // WithAddress adds an address for a known node in the cluster to this configuration's list of addresses. WithAddress can be // called multiple times to add multiple addresses to the list. If the server is in cluster mode the list can be partial, as // the client will attempt to map out the cluster and find all nodes. If the server is in standalone mode, only nodes whose -// addresses were provided will be used by the client. For example: +// addresses were provided will be used by the client. +// +// For example: // -// config := NewRedisClusterClientConfiguration(). +// config := NewGlideClusterClientConfiguration(). // WithAddress(&NodeAddress{ // Host: "sample-address-0001.use1.cache.amazonaws.com", Port: 6379}). // WithAddress(&NodeAddress{ // Host: "sample-address-0002.use1.cache.amazonaws.com", Port: 6379}) -func (config *RedisClusterClientConfiguration) WithAddress(address *NodeAddress) *RedisClusterClientConfiguration { +func (config *GlideClusterClientConfiguration) WithAddress(address *NodeAddress) *GlideClusterClientConfiguration { config.addresses = append(config.addresses, *address) return config } @@ -272,22 +275,22 @@ func (config *RedisClusterClientConfiguration) WithAddress(address *NodeAddress) // WithUseTLS configures the TLS settings for this configuration. Set to true if communication with the cluster should use // Transport Level Security. This setting should match the TLS configuration of the server/cluster, otherwise the connection // attempt will fail. -func (config *RedisClusterClientConfiguration) WithUseTLS(useTLS bool) *RedisClusterClientConfiguration { +func (config *GlideClusterClientConfiguration) WithUseTLS(useTLS bool) *GlideClusterClientConfiguration { config.useTLS = useTLS return config } // WithCredentials sets the credentials for the authentication process. If none are set, the client will not authenticate // itself with the server. -func (config *RedisClusterClientConfiguration) WithCredentials( - credentials *RedisCredentials, -) *RedisClusterClientConfiguration { +func (config *GlideClusterClientConfiguration) WithCredentials( + credentials *ServerCredentials, +) *GlideClusterClientConfiguration { config.credentials = credentials return config } // WithReadFrom sets the client's [ReadFrom] strategy. If not set, [Primary] will be used. -func (config *RedisClusterClientConfiguration) WithReadFrom(readFrom ReadFrom) *RedisClusterClientConfiguration { +func (config *GlideClusterClientConfiguration) WithReadFrom(readFrom ReadFrom) *GlideClusterClientConfiguration { config.readFrom = readFrom return config } @@ -296,14 +299,14 @@ func (config *RedisClusterClientConfiguration) WithReadFrom(readFrom ReadFrom) * // encompasses sending the request, awaiting for a response from the server, and any required reconnections or retries. If the // specified timeout is exceeded for a pending request, it will result in a timeout error. If not set, a default value will be // used. -func (config *RedisClusterClientConfiguration) WithRequestTimeout(requestTimeout int) *RedisClusterClientConfiguration { +func (config *GlideClusterClientConfiguration) WithRequestTimeout(requestTimeout int) *GlideClusterClientConfiguration { config.requestTimeout = requestTimeout return config } // WithClientName sets the client name to be used for the client. Will be used with CLIENT SETNAME command during connection // establishment. -func (config *RedisClusterClientConfiguration) WithClientName(clientName string) *RedisClusterClientConfiguration { +func (config *GlideClusterClientConfiguration) WithClientName(clientName string) *GlideClusterClientConfiguration { config.clientName = clientName return config } diff --git a/go/api/config_test.go b/go/api/config_test.go index 44e6530d9d..dcb778798e 100644 --- a/go/api/config_test.go +++ b/go/api/config_test.go @@ -11,7 +11,7 @@ import ( ) func TestDefaultStandaloneConfig(t *testing.T) { - config := NewRedisClientConfiguration() + config := NewGlideClientConfiguration() expected := &protobuf.ConnectionRequest{ TlsMode: protobuf.TlsMode_NoTls, ClusterModeEnabled: false, @@ -24,7 +24,7 @@ func TestDefaultStandaloneConfig(t *testing.T) { } func TestDefaultClusterConfig(t *testing.T) { - config := NewRedisClusterClientConfiguration() + config := NewGlideClusterClientConfiguration() expected := &protobuf.ConnectionRequest{ TlsMode: protobuf.TlsMode_NoTls, ClusterModeEnabled: true, @@ -46,10 +46,10 @@ func TestConfig_allFieldsSet(t *testing.T) { retries, factor, base := 5, 10, 50 databaseId := 1 - config := NewRedisClientConfiguration(). + config := NewGlideClientConfiguration(). WithUseTLS(true). WithReadFrom(PreferReplica). - WithCredentials(NewRedisCredentials(username, password)). + WithCredentials(NewServerCredentials(username, password)). WithRequestTimeout(timeout). WithClientName(clientName). WithReconnectStrategy(NewBackoffStrategy(retries, factor, base)). @@ -104,17 +104,17 @@ func TestNodeAddress(t *testing.T) { } } -func TestRedisCredentials(t *testing.T) { +func TestServerCredentials(t *testing.T) { parameters := []struct { - input *RedisCredentials + input *ServerCredentials expected *protobuf.AuthenticationInfo }{ { - NewRedisCredentials("username", "password"), + NewServerCredentials("username", "password"), &protobuf.AuthenticationInfo{Username: "username", Password: "password"}, }, { - NewRedisCredentialsWithDefaultUsername("password"), + NewServerCredentialsWithDefaultUsername("password"), &protobuf.AuthenticationInfo{Password: "password"}, }, } diff --git a/go/api/errors.go b/go/api/errors.go new file mode 100644 index 0000000000..4fa4fad92a --- /dev/null +++ b/go/api/errors.go @@ -0,0 +1,65 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package api + +// #cgo LDFLAGS: -L../target/release -lglide_rs +// #include "../lib.h" +import "C" + +// ConnectionError is a client error that occurs when there is an error while connecting or when a connection +// disconnects. +type ConnectionError struct { + msg string +} + +func (e *ConnectionError) Error() string { return e.msg } + +// RequestError is a client error that occurs when an error is reported during a request. +type RequestError struct { + msg string +} + +func (e *RequestError) Error() string { return e.msg } + +// ExecAbortError is a client error that occurs when a transaction is aborted. +type ExecAbortError struct { + msg string +} + +func (e *ExecAbortError) Error() string { return e.msg } + +// TimeoutError is a client error that occurs when a request times out. +type TimeoutError struct { + msg string +} + +func (e *TimeoutError) Error() string { return e.msg } + +// DisconnectError is a client error that indicates a connection problem between Glide and server. +type DisconnectError struct { + msg string +} + +func (e *DisconnectError) Error() string { return e.msg } + +// ClosingError is a client error that indicates that the client has closed and is no longer usable. +type ClosingError struct { + msg string +} + +func (e *ClosingError) Error() string { return e.msg } + +func goError(cErrorType C.RequestErrorType, cErrorMessage *C.char) error { + defer C.free_error_message(cErrorMessage) + msg := C.GoString(cErrorMessage) + switch cErrorType { + case C.ExecAbort: + return &ExecAbortError{msg} + case C.Timeout: + return &TimeoutError{msg} + case C.Disconnect: + return &DisconnectError{msg} + default: + return &RequestError{msg} + } +} diff --git a/go/api/glide_client.go b/go/api/glide_client.go new file mode 100644 index 0000000000..7dfb23cc02 --- /dev/null +++ b/go/api/glide_client.go @@ -0,0 +1,43 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package api + +// #cgo LDFLAGS: -L../target/release -lglide_rs +// #include "../lib.h" +import "C" + +// GlideClient is a client used for connection in Standalone mode. +type GlideClient struct { + *baseClient +} + +// NewGlideClient creates a [GlideClient] in standalone mode using the given [GlideClientConfiguration]. +func NewGlideClient(config *GlideClientConfiguration) (*GlideClient, error) { + client, err := createClient(config) + if err != nil { + return nil, err + } + + return &GlideClient{client}, nil +} + +// CustomCommand executes a single command, specified by args, without checking inputs. Every part of the command, including +// the command name and subcommands, should be added as a separate value in args. The returning value depends on the executed +// command. +// +// This function should only be used for single-response commands. Commands that don't return complete response and awaits +// (such as SUBSCRIBE), or that return potentially more than a single response (such as XREAD), or that change the client's +// behavior (such as entering pub/sub mode on RESP2 connections) shouldn't be called using this function. +// +// For example, to return a list of all pub/sub clients: +// +// client.CustomCommand([]string{"CLIENT", "LIST","TYPE", "PUBSUB"}) +// +// TODO: Add support for complex return types. +func (client *GlideClient) CustomCommand(args []string) (interface{}, error) { + res, err := client.executeCommand(C.CustomCommand, args) + if err != nil { + return nil, err + } + return handleStringOrNullResponse(res), nil +} diff --git a/go/api/glide_cluster_client.go b/go/api/glide_cluster_client.go new file mode 100644 index 0000000000..7ab186d7c2 --- /dev/null +++ b/go/api/glide_cluster_client.go @@ -0,0 +1,18 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package api + +// GlideClusterClient is a client used for connection in cluster mode. +type GlideClusterClient struct { + *baseClient +} + +// NewGlideClusterClient creates a [GlideClusterClient] in cluster mode using the given [GlideClusterClientConfiguration]. +func NewGlideClusterClient(config *GlideClusterClientConfiguration) (*GlideClusterClient, error) { + client, err := createClient(config) + if err != nil { + return nil, err + } + + return &GlideClusterClient{client}, nil +} diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go new file mode 100644 index 0000000000..524c7e057a --- /dev/null +++ b/go/api/response_handlers.go @@ -0,0 +1,32 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package api + +// #cgo LDFLAGS: -L../target/release -lglide_rs +// #include "../lib.h" +import "C" + +import ( + "unsafe" +) + +func convertCharArrayToString(arr *C.char, length C.long) string { + if arr == nil { + return "" + } + byteSlice := C.GoBytes(unsafe.Pointer(arr), C.int(int64(length))) + // Create Go string from byte slice (preserving null characters) + return string(byteSlice) +} + +func handleStringResponse(response *C.struct_CommandResponse) string { + defer C.free_command_response(response) + return convertCharArrayToString(response.string_value, response.string_value_len) +} + +func handleStringOrNullResponse(response *C.struct_CommandResponse) string { + if response == nil { + return "" + } + return handleStringResponse(response) +} diff --git a/go/benchmarks/benchmarking.go b/go/benchmarks/benchmarking.go new file mode 100644 index 0000000000..68061bd1b8 --- /dev/null +++ b/go/benchmarks/benchmarking.go @@ -0,0 +1,460 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package main + +import ( + "crypto/rand" + "encoding/json" + "fmt" + "log" + "math" + "math/big" + "os" + "sort" + "strings" + "time" +) + +type connectionSettings struct { + host string + port int + useTLS bool + clusterModeEnabled bool +} + +func runBenchmarks(runConfig *runConfiguration) error { + connSettings := &connectionSettings{ + host: runConfig.host, + port: runConfig.port, + useTLS: runConfig.tls, + clusterModeEnabled: runConfig.clusterModeEnabled, + } + + err := executeBenchmarks(runConfig, connSettings) + if err != nil { + return err + } + + if runConfig.resultsFile != os.Stdout { + return writeResults(runConfig.resultsFile) + } + + return nil +} + +type benchmarkConfig struct { + clientName string + numConcurrentTasks int + clientCount int + dataSize int + minimal bool + connectionSettings *connectionSettings + resultsFile *os.File +} + +func executeBenchmarks(runConfig *runConfiguration, connectionSettings *connectionSettings) error { + var benchmarkConfigs []benchmarkConfig + for _, clientName := range runConfig.clientNames { + for _, numConcurrentTasks := range runConfig.concurrentTasks { + for _, clientCount := range runConfig.clientCount { + for _, dataSize := range runConfig.dataSize { + benchmarkConfig := benchmarkConfig{ + clientName: clientName, + numConcurrentTasks: numConcurrentTasks, + clientCount: clientCount, + dataSize: dataSize, + minimal: runConfig.minimal, + connectionSettings: connectionSettings, + resultsFile: runConfig.resultsFile, + } + + benchmarkConfigs = append(benchmarkConfigs, benchmarkConfig) + } + } + } + } + + for _, config := range benchmarkConfigs { + err := runSingleBenchmark(&config) + if err != nil { + return err + } + } + + return nil +} + +func runSingleBenchmark(config *benchmarkConfig) error { + fmt.Printf("Running benchmarking for %s client:\n", config.clientName) + fmt.Printf( + "\n =====> %s <===== clientCount: %d, concurrentTasks: %d, dataSize: %d \n\n", + config.clientName, + config.clientCount, + config.numConcurrentTasks, + config.dataSize, + ) + + clients, err := createClients(config) + if err != nil { + return err + } + + benchmarkResult := measureBenchmark(clients, config) + if config.resultsFile != os.Stdout { + addJsonResults(config, benchmarkResult) + } + + printResults(benchmarkResult) + fmt.Println() + return closeClients(clients) +} + +type benchmarkClient interface { + connect(connectionSettings *connectionSettings) error + set(key string, value string) (string, error) + get(key string) (string, error) + close() error + getName() string +} + +func createClients(config *benchmarkConfig) ([]benchmarkClient, error) { + var clients []benchmarkClient + for clientNum := 0; clientNum < config.clientCount; clientNum++ { + var client benchmarkClient + switch config.clientName { + case goRedis: + client = &goRedisBenchmarkClient{} + case glide: + client = &glideBenchmarkClient{} + } + + err := client.connect(config.connectionSettings) + if err != nil { + return nil, err + } + + clients = append(clients, client) + } + + return clients, nil +} + +func closeClients(clients []benchmarkClient) error { + for _, client := range clients { + err := client.close() + if err != nil { + return err + } + } + + return nil +} + +var jsonResults []map[string]interface{} + +func writeResults(file *os.File) error { + fileInfo, err := file.Stat() + if err != nil { + return err + } + + if fileInfo.Size() != 0 { + decoder := json.NewDecoder(file) + var existingData []map[string]interface{} + err = decoder.Decode(&existingData) + if err != nil { + return err + } + + jsonResults = append(existingData, jsonResults...) + } + + marshalledJson, err := json.Marshal(jsonResults) + if err != nil { + return err + } + _, err = file.WriteAt(marshalledJson, 0) + if err != nil { + return err + } + + return nil +} + +type benchmarkResults struct { + iterationsPerTask int + duration time.Duration + tps float64 + latencyStats map[string]*latencyStats +} + +func measureBenchmark(clients []benchmarkClient, config *benchmarkConfig) *benchmarkResults { + var iterationsPerTask int + if config.minimal { + iterationsPerTask = 1000 + } else { + iterationsPerTask = int(math.Min(math.Max(1e5, float64(config.numConcurrentTasks*1e4)), 1e7)) + } + + actions := getActions(config.dataSize) + duration, latencies := runBenchmark(iterationsPerTask, config.numConcurrentTasks, actions, clients) + tps := calculateTPS(latencies, duration) + stats := getLatencyStats(latencies) + return &benchmarkResults{ + iterationsPerTask: iterationsPerTask, + duration: duration, + tps: tps, + latencyStats: stats, + } +} + +func calculateTPS(latencies map[string][]time.Duration, totalDuration time.Duration) float64 { + numRequests := 0 + for _, durations := range latencies { + numRequests += len(durations) + } + + return float64(numRequests) / totalDuration.Seconds() +} + +type operations func(client benchmarkClient) (string, error) + +const ( + getExisting = "get_existing" + getNonExisting = "get_non_existing" + set = "set" +) + +func getActions(dataSize int) map[string]operations { + actions := map[string]operations{ + getExisting: func(client benchmarkClient) (string, error) { + return client.get(keyFromExistingKeyspace()) + }, + getNonExisting: func(client benchmarkClient) (string, error) { + return client.get(keyFromNewKeyspace()) + }, + set: func(client benchmarkClient) (string, error) { + return client.set(keyFromExistingKeyspace(), strings.Repeat("0", dataSize)) + }, + } + + return actions +} + +const ( + sizeNewKeyspace = 3750000 + sizeExistingKeyspace = 3000000 +) + +func keyFromExistingKeyspace() string { + randNum, err := rand.Int(rand.Reader, big.NewInt(sizeExistingKeyspace)) + if err != nil { + log.Fatal("Error while generating random number for existing keyspace: ", err) + } + + return fmt.Sprint(randNum.Int64() + 1) +} + +func keyFromNewKeyspace() string { + var totalRange int64 = sizeNewKeyspace - sizeExistingKeyspace + randNum, err := rand.Int(rand.Reader, big.NewInt(totalRange)) + if err != nil { + log.Fatal("Error while generating random number for existing keyspace: ", err) + } + + return fmt.Sprint(randNum.Int64() + sizeExistingKeyspace + 1) +} + +type actionLatency struct { + action string + latency time.Duration +} + +func runBenchmark( + iterationsPerTask int, + concurrentTasks int, + actions map[string]operations, + clients []benchmarkClient, +) (totalDuration time.Duration, latencies map[string][]time.Duration) { + latencies = map[string][]time.Duration{ + getExisting: {}, + getNonExisting: {}, + set: {}, + } + + numResults := concurrentTasks * iterationsPerTask + results := make(chan *actionLatency, numResults) + start := time.Now() + for i := 0; i < concurrentTasks; i++ { + go runTask(results, iterationsPerTask, actions, clients) + } + + for i := 0; i < numResults; i++ { + result := <-results + latencies[result.action] = append(latencies[result.action], result.latency) + } + + return time.Since(start), latencies +} + +func runTask(results chan<- *actionLatency, iterations int, actions map[string]operations, clients []benchmarkClient) { + for i := 0; i < iterations; i++ { + clientIndex := i % len(clients) + action := randomAction() + operation := actions[action] + latency := measureOperation(operation, clients[clientIndex]) + results <- &actionLatency{action: action, latency: latency} + } +} + +func measureOperation(operation operations, client benchmarkClient) time.Duration { + start := time.Now() + _, err := operation(client) + duration := time.Since(start) + if err != nil { + log.Print("error while executing operation: ", err) + } + + return duration +} + +const ( + probGet = 0.8 + probGetExistingKey = 0.8 +) + +// randFloat generates a random float64 in the range [0.0, 1.0) +func randFloat() float64 { + // 1 << 53 is used because a float64 value contains 53 bits for the mantissa and 11 other bits. + randInt, err := rand.Int(rand.Reader, big.NewInt(1<<53)) + if err != nil { + log.Fatal("Error creating random float64: ", err) + } + return float64(randInt.Int64()) / (1 << 53) +} + +func randomAction() string { + if randFloat() > probGet { + return set + } + + if randFloat() > probGetExistingKey { + return getNonExisting + } + + return getExisting +} + +type latencyStats struct { + avgLatency time.Duration + p50Latency time.Duration + p90Latency time.Duration + p99Latency time.Duration + stdDeviation time.Duration + numRequests int +} + +func getLatencyStats(actionLatencies map[string][]time.Duration) map[string]*latencyStats { + results := make(map[string]*latencyStats) + + for action, latencies := range actionLatencies { + sort.Slice(latencies, func(i, j int) bool { + return latencies[i] < latencies[j] + }) + + results[action] = &latencyStats{ + // TODO: Replace with a stats library, eg https://pkg.go.dev/github.com/montanaflynn/stats + avgLatency: average(latencies), + p50Latency: percentile(latencies, 50), + p90Latency: percentile(latencies, 90), + p99Latency: percentile(latencies, 99), + stdDeviation: standardDeviation(latencies), + numRequests: len(latencies), + } + } + + return results +} + +func average(observations []time.Duration) time.Duration { + var sumNano int64 = 0 + for _, observation := range observations { + sumNano += observation.Nanoseconds() + } + + avgNano := sumNano / int64(len(observations)) + return time.Duration(avgNano) +} + +func percentile(observations []time.Duration, p float64) time.Duration { + N := float64(len(observations)) + n := (N-1)*p/100 + 1 + + if n == 1.0 { + return observations[0] + } else if n == N { + return observations[int(N)-1] + } + + k := int(n) + d := n - float64(k) + interpolatedValue := float64(observations[k-1]) + d*(float64(observations[k])-float64(observations[k-1])) + return time.Duration(int64(math.Round(interpolatedValue))) +} + +func standardDeviation(observations []time.Duration) time.Duration { + var sum, mean, sd float64 + numObservations := len(observations) + for i := 0; i < numObservations; i++ { + sum += float64(observations[i]) + } + + mean = sum / float64(numObservations) + for j := 0; j < numObservations; j++ { + sd += math.Pow(float64(observations[j])-mean, 2) + } + + sd = math.Sqrt(sd / float64(numObservations)) + return time.Duration(sd) +} + +func printResults(results *benchmarkResults) { + fmt.Printf("Runtime (sec): %.3f\n", results.duration.Seconds()) + fmt.Printf("Iterations: %d\n", results.iterationsPerTask) + fmt.Printf("TPS: %d\n", int(results.tps)) + + var totalRequests int + for action, latencyStat := range results.latencyStats { + fmt.Printf("===> %s <===\n", action) + fmt.Printf("avg. latency (ms): %.3f\n", latencyStat.avgLatency.Seconds()*1000) + fmt.Printf("std dev (ms): %.3f\n", latencyStat.stdDeviation.Seconds()*1000) + fmt.Printf("p50 latency (ms): %.3f\n", latencyStat.p50Latency.Seconds()*1000) + fmt.Printf("p90 latency (ms): %.3f\n", latencyStat.p90Latency.Seconds()*1000) + fmt.Printf("p99 latency (ms): %.3f\n", latencyStat.p99Latency.Seconds()*1000) + fmt.Printf("Number of requests: %d\n", latencyStat.numRequests) + totalRequests += latencyStat.numRequests + } + + fmt.Printf("Total requests: %d\n", totalRequests) +} + +func addJsonResults(config *benchmarkConfig, results *benchmarkResults) { + jsonResult := make(map[string]interface{}) + + jsonResult["client"] = config.clientName + jsonResult["is_cluster"] = config.connectionSettings.clusterModeEnabled + jsonResult["num_of_tasks"] = config.numConcurrentTasks + jsonResult["data_size"] = config.dataSize + jsonResult["client_count"] = config.clientCount + jsonResult["tps"] = results.tps + + for key, value := range results.latencyStats { + jsonResult[key+"_p50_latency"] = value.p50Latency.Seconds() * 1000 + jsonResult[key+"_p90_latency"] = value.p90Latency.Seconds() * 1000 + jsonResult[key+"_p99_latency"] = value.p99Latency.Seconds() * 1000 + jsonResult[key+"_average_latency"] = value.avgLatency.Seconds() * 1000 + jsonResult[key+"_std_dev"] = value.stdDeviation.Seconds() * 1000 + } + + jsonResults = append(jsonResults, jsonResult) +} diff --git a/go/benchmarks/glide_benchmark_client.go b/go/benchmarks/glide_benchmark_client.go new file mode 100644 index 0000000000..6ae9f4d8d4 --- /dev/null +++ b/go/benchmarks/glide_benchmark_client.go @@ -0,0 +1,54 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package main + +import ( + "github.com/valkey-io/valkey-glide/go/glide/api" +) + +type glideBenchmarkClient struct { + client api.BaseClient +} + +func (glideBenchmarkClient *glideBenchmarkClient) connect(connectionSettings *connectionSettings) error { + if connectionSettings.clusterModeEnabled { + config := api.NewGlideClusterClientConfiguration(). + WithAddress(&api.NodeAddress{Host: connectionSettings.host, Port: connectionSettings.port}). + WithUseTLS(connectionSettings.useTLS) + glideClient, err := api.NewGlideClusterClient(config) + if err != nil { + return err + } + + glideBenchmarkClient.client = glideClient + return nil + } else { + config := api.NewGlideClientConfiguration(). + WithAddress(&api.NodeAddress{Host: connectionSettings.host, Port: connectionSettings.port}). + WithUseTLS(connectionSettings.useTLS) + glideClient, err := api.NewGlideClient(config) + if err != nil { + return err + } + + glideBenchmarkClient.client = glideClient + return nil + } +} + +func (glideBenchmarkClient *glideBenchmarkClient) get(key string) (string, error) { + return glideBenchmarkClient.client.Get(key) +} + +func (glideBenchmarkClient *glideBenchmarkClient) set(key string, value string) (string, error) { + return glideBenchmarkClient.client.Set(key, value) +} + +func (glideBenchmarkClient *glideBenchmarkClient) close() error { + glideBenchmarkClient.client.Close() + return nil +} + +func (glideBenchmarkClient *glideBenchmarkClient) getName() string { + return "glide" +} diff --git a/go/benchmarks/go.mod b/go/benchmarks/go.mod new file mode 100644 index 0000000000..93dc587fb3 --- /dev/null +++ b/go/benchmarks/go.mod @@ -0,0 +1,16 @@ +module github.com/valkey-io/valkey-glide/go/glide/benchmarks + +go 1.18 + +replace github.com/valkey-io/valkey-glide/go/glide => ../ + +require ( + github.com/valkey-io/valkey-glide/go/glide v0.0.0 + github.com/redis/go-redis/v9 v9.5.1 +) + +require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + google.golang.org/protobuf v1.33.0 // indirect +) diff --git a/go/benchmarks/go.sum b/go/benchmarks/go.sum new file mode 100644 index 0000000000..3d1b358231 --- /dev/null +++ b/go/benchmarks/go.sum @@ -0,0 +1,15 @@ +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= +github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/go/benchmarks/go_redis_benchmark_client.go b/go/benchmarks/go_redis_benchmark_client.go new file mode 100644 index 0000000000..1909753fce --- /dev/null +++ b/go/benchmarks/go_redis_benchmark_client.go @@ -0,0 +1,71 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package main + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + + "github.com/redis/go-redis/v9" +) + +type goRedisBenchmarkClient struct { + client redis.Cmdable +} + +func (goRedisClient *goRedisBenchmarkClient) connect(connectionSettings *connectionSettings) error { + if connectionSettings.clusterModeEnabled { + clusterOptions := &redis.ClusterOptions{ + Addrs: []string{fmt.Sprintf("%s:%d", connectionSettings.host, connectionSettings.port)}, + } + + if connectionSettings.useTLS { + clusterOptions.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12} + } + + goRedisClient.client = redis.NewClusterClient(clusterOptions) + } else { + options := &redis.Options{ + Addr: fmt.Sprintf("%s:%d", connectionSettings.host, connectionSettings.port), + DB: 0, + } + + if connectionSettings.useTLS { + options.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12} + } + + goRedisClient.client = redis.NewClient(options) + } + + return goRedisClient.client.Ping(context.Background()).Err() +} + +func (goRedisClient *goRedisBenchmarkClient) set(key string, value string) (string, error) { + return goRedisClient.client.Set(context.Background(), key, value, 0).Result() +} + +func (goRedisClient *goRedisBenchmarkClient) get(key string) (string, error) { + value, err := goRedisClient.client.Get(context.Background(), key).Result() + if err != nil && !errors.Is(err, redis.Nil) { + return "", err + } + + return value, nil +} + +func (goRedisClient *goRedisBenchmarkClient) close() error { + switch c := goRedisClient.client.(type) { + case *redis.Client: + return c.Close() + case *redis.ClusterClient: + return c.Close() + default: + return fmt.Errorf("unsupported client type") + } +} + +func (goRedisClient *goRedisBenchmarkClient) getName() string { + return "go-redis" +} diff --git a/go/benchmarks/main.go b/go/benchmarks/main.go new file mode 100644 index 0000000000..4614334290 --- /dev/null +++ b/go/benchmarks/main.go @@ -0,0 +1,195 @@ +// Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 + +package main + +import ( + "errors" + "flag" + "fmt" + "log" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" +) + +type options struct { + resultsFile string + dataSize string + concurrentTasks string + clients string + host string + port int + clientCount string + tls bool + clusterModeEnabled bool + minimal bool +} + +type runConfiguration struct { + resultsFile *os.File + dataSize []int + concurrentTasks []int + clientNames []string + host string + port int + clientCount []int + tls bool + clusterModeEnabled bool + minimal bool +} + +const ( + goRedis = "go-redis" + glide = "glide" + all = "all" +) + +func main() { + opts := parseArguments() + + runConfig, err := verifyOptions(opts) + if err != nil { + log.Fatal("Error verifying options:", err) + return + } + + if runConfig.resultsFile != os.Stdout { + defer closeFile(runConfig.resultsFile) + } + + err = runBenchmarks(runConfig) + if err != nil { + log.Fatal("Error running benchmarking:", err) + } +} + +func closeFile(file *os.File) { + err := file.Close() + if err != nil { + log.Fatal("Error closing file:", err) + } +} + +func parseArguments() *options { + resultsFile := flag.String("resultsFile", "results/go-results.json", "Result filepath") + dataSize := flag.String("dataSize", "[100]", "Data block size") + concurrentTasks := flag.String("concurrentTasks", "[1 10 100 1000]", "Number of concurrent tasks") + clientNames := flag.String("clients", "all", "One of: all|go-redis|glide") + host := flag.String("host", "localhost", "Hostname") + port := flag.Int("port", 6379, "Port number") + clientCount := flag.String("clientCount", "[1]", "Number of clients to run") + tls := flag.Bool("tls", false, "Use TLS") + clusterModeEnabled := flag.Bool("clusterModeEnabled", false, "Is cluster mode enabled") + minimal := flag.Bool("minimal", false, "Run benchmark in minimal mode") + + flag.Parse() + + return &options{ + resultsFile: *resultsFile, + dataSize: *dataSize, + concurrentTasks: *concurrentTasks, + clients: *clientNames, + host: *host, + port: *port, + clientCount: *clientCount, + tls: *tls, + clusterModeEnabled: *clusterModeEnabled, + minimal: *minimal, + } +} + +func verifyOptions(opts *options) (*runConfiguration, error) { + var runConfig runConfiguration + var err error + + if opts.resultsFile == "" { + runConfig.resultsFile = os.Stdout + } else if _, err = os.Stat(opts.resultsFile); err == nil { + // File exists + runConfig.resultsFile, err = os.OpenFile(opts.resultsFile, os.O_RDWR, os.ModePerm) + if err != nil { + return nil, err + } + } else if errors.Is(err, os.ErrNotExist) { + // File does not exist + err = os.MkdirAll(filepath.Dir(opts.resultsFile), os.ModePerm) + if err != nil { + return nil, err + } + + runConfig.resultsFile, err = os.Create(opts.resultsFile) + if err != nil { + return nil, err + } + } else { + // Some other error occurred + return nil, err + } + + runConfig.concurrentTasks, err = parseOptionsIntList(opts.concurrentTasks) + if err != nil { + return nil, fmt.Errorf("invalid concurrentTasks option: %v", err) + } + + runConfig.dataSize, err = parseOptionsIntList(opts.dataSize) + if err != nil { + return nil, fmt.Errorf("invalid dataSize option: %v", err) + } + + runConfig.clientCount, err = parseOptionsIntList(opts.clientCount) + if err != nil { + return nil, fmt.Errorf("invalid clientCount option: %v", err) + } + + switch { + case strings.EqualFold(opts.clients, goRedis): + runConfig.clientNames = append(runConfig.clientNames, goRedis) + + case strings.EqualFold(opts.clients, glide): + runConfig.clientNames = append(runConfig.clientNames, glide) + + case strings.EqualFold(opts.clients, all): + runConfig.clientNames = append(runConfig.clientNames, goRedis, glide) + default: + return nil, fmt.Errorf("invalid clients option, should be one of: all|go-redis|glide") + } + + runConfig.host = opts.host + runConfig.port = opts.port + runConfig.tls = opts.tls + runConfig.clusterModeEnabled = opts.clusterModeEnabled + runConfig.minimal = opts.minimal + + return &runConfig, nil +} + +func parseOptionsIntList(listAsString string) ([]int, error) { + listAsString = strings.Trim(strings.TrimSpace(listAsString), "[]") + if len(listAsString) == 0 { + return nil, fmt.Errorf("option is empty or contains only brackets") + } + + matched, err := regexp.MatchString("^\\d+(\\s+\\d+)*$", listAsString) + if err != nil { + return nil, err + } + + if !matched { + return nil, fmt.Errorf("wrong format for option") + } + + stringList := strings.Split(listAsString, " ") + var intList []int + for _, intString := range stringList { + num, err := strconv.Atoi(strings.TrimSpace(intString)) + if err != nil { + return nil, fmt.Errorf("wrong number format for option: %s", intString) + } + + intList = append(intList, num) + } + + return intList, nil +} diff --git a/go/examples/main.go b/go/examples/main.go new file mode 100644 index 0000000000..d2b9761335 --- /dev/null +++ b/go/examples/main.go @@ -0,0 +1,56 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package main + +import ( + "fmt" + "log" + + "github.com/valkey-io/valkey-glide/go/glide/api" +) + +// TODO: Update the file based on the template used in other clients. +func main() { + host := "localhost" + port := 6379 + + config := api.NewGlideClientConfiguration(). + WithAddress(&api.NodeAddress{Host: host, Port: port}) + + client, err := api.NewGlideClient(config) + if err != nil { + log.Fatal("error connecting to database: ", err) + } + + res, err := client.CustomCommand([]string{"PING"}) + if err != nil { + log.Fatal("Glide example failed with an error: ", err) + } + fmt.Println("PING:", res) + + res, err = client.Set("apples", "oran\x00ges") + if err != nil { + log.Fatal("Glide example failed with an error: ", err) + } + fmt.Println("SET(apples, oranges):", res) + + res, err = client.Get("invalidKey") + if err != nil { + log.Fatal("Glide example failed with an error: ", err) + } + fmt.Println("GET(invalidKey):", res) + + res, err = client.Get("apples") + if err != nil { + log.Fatal("Glide example failed with an error: ", err) + } + fmt.Println("GET(apples):", res) + + res, err = client.Get("app\x00les") + if err != nil { + log.Fatal("Glide example failed with an error: ", err) + } + fmt.Println("GET(app\x00les):", res) + + client.Close() +} diff --git a/go/go.mod b/go/go.mod index 21aaf9f4a7..cbca0b10fa 100644 --- a/go/go.mod +++ b/go/go.mod @@ -1,6 +1,6 @@ module github.com/valkey-io/valkey-glide/go/glide -go 1.18 +go 1.20 require ( github.com/stretchr/testify v1.8.4 @@ -10,6 +10,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect diff --git a/go/go.sum b/go/go.sum index cdbe2b7522..bb96255d9f 100644 --- a/go/go.sum +++ b/go/go.sum @@ -3,6 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= diff --git a/go/integTest/connection_test.go b/go/integTest/connection_test.go new file mode 100644 index 0000000000..910772769b --- /dev/null +++ b/go/integTest/connection_test.go @@ -0,0 +1,55 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package integTest + +import ( + "github.com/stretchr/testify/assert" + "github.com/valkey-io/valkey-glide/go/glide/api" +) + +func (suite *GlideTestSuite) TestStandaloneConnect() { + config := api.NewGlideClientConfiguration(). + WithAddress(&api.NodeAddress{Port: suite.standalonePorts[0]}) + client, err := api.NewGlideClient(config) + + assert.Nil(suite.T(), err) + assert.NotNil(suite.T(), client) + + client.Close() +} + +func (suite *GlideTestSuite) TestClusterConnect() { + config := api.NewGlideClusterClientConfiguration() + for _, port := range suite.clusterPorts { + config.WithAddress(&api.NodeAddress{Port: port}) + } + + client, err := api.NewGlideClusterClient(config) + + assert.Nil(suite.T(), err) + assert.NotNil(suite.T(), client) + + client.Close() +} + +func (suite *GlideTestSuite) TestClusterConnect_singlePort() { + config := api.NewGlideClusterClientConfiguration(). + WithAddress(&api.NodeAddress{Port: suite.clusterPorts[0]}) + + client, err := api.NewGlideClusterClient(config) + + assert.Nil(suite.T(), err) + assert.NotNil(suite.T(), client) + + client.Close() +} + +func (suite *GlideTestSuite) TestConnectWithInvalidAddress() { + config := api.NewGlideClientConfiguration(). + WithAddress(&api.NodeAddress{Host: "invalid-host"}) + client, err := api.NewGlideClient(config) + + assert.Nil(suite.T(), client) + assert.NotNil(suite.T(), err) + assert.IsType(suite.T(), &api.ConnectionError{}, err) +} diff --git a/go/integTest/glide_test_suite_test.go b/go/integTest/glide_test_suite_test.go new file mode 100644 index 0000000000..7abf5df27c --- /dev/null +++ b/go/integTest/glide_test_suite_test.go @@ -0,0 +1,192 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package integTest + +import ( + "errors" + "fmt" + "log" + "os" + "os/exec" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + "github.com/valkey-io/valkey-glide/go/glide/api" +) + +type GlideTestSuite struct { + suite.Suite + standalonePorts []int + clusterPorts []int + redisVersion string + clients []*api.GlideClient + clusterClients []*api.GlideClusterClient +} + +func (suite *GlideTestSuite) SetupSuite() { + // Stop cluster in case previous test run was interrupted or crashed and didn't stop. + // If an error occurs, we ignore it in case the servers actually were stopped before running this. + runClusterManager(suite, []string{"stop", "--prefix", "redis-cluster"}, true) + + // Delete dirs if stop failed due to https://github.com/valkey-io/valkey-glide/issues/849 + err := os.RemoveAll("../../utils/clusters") + if err != nil && !os.IsNotExist(err) { + log.Fatal(err) + } + + // Start standalone instance + clusterManagerOutput := runClusterManager(suite, []string{"start", "-r", "0"}, false) + + suite.standalonePorts = extractPorts(suite, clusterManagerOutput) + suite.T().Logf("Standalone ports = %s", fmt.Sprint(suite.standalonePorts)) + + // Start cluster + clusterManagerOutput = runClusterManager(suite, []string{"start", "--cluster-mode"}, false) + + suite.clusterPorts = extractPorts(suite, clusterManagerOutput) + suite.T().Logf("Cluster ports = %s", fmt.Sprint(suite.clusterPorts)) + + // Get Redis version + byteOutput, err := exec.Command("redis-server", "-v").Output() + if err != nil { + suite.T().Fatal(err.Error()) + } + + suite.redisVersion = extractServerVersion(string(byteOutput)) + suite.T().Logf("Redis version = %s", suite.redisVersion) +} + +func extractPorts(suite *GlideTestSuite, output string) []int { + var ports []int + for _, line := range strings.Split(output, "\n") { + if !strings.HasPrefix(line, "CLUSTER_NODES=") { + continue + } + + addresses := strings.Split(line, "=")[1] + addressList := strings.Split(addresses, ",") + for _, address := range addressList { + portString := strings.Split(address, ":")[1] + port, err := strconv.Atoi(portString) + if err != nil { + suite.T().Fatalf("Failed to parse port from cluster_manager.py output: %s", err.Error()) + } + + ports = append(ports, port) + } + } + + return ports +} + +func runClusterManager(suite *GlideTestSuite, args []string, ignoreExitCode bool) string { + pythonArgs := append([]string{"../../utils/cluster_manager.py"}, args...) + output, err := exec.Command("python3", pythonArgs...).Output() + if len(output) > 0 { + suite.T().Logf("cluster_manager.py stdout:\n====\n%s\n====\n", string(output)) + } + + if err != nil { + var exitError *exec.ExitError + isExitError := errors.As(err, &exitError) + if !isExitError { + suite.T().Fatalf("Unexpected error while executing cluster_manager.py: %s", err.Error()) + } + + if exitError.Stderr != nil && len(exitError.Stderr) > 0 { + suite.T().Logf("cluster_manager.py stderr:\n====\n%s\n====\n", string(exitError.Stderr)) + } + + if !ignoreExitCode { + suite.T().Fatalf("cluster_manager.py script failed: %s", exitError.Error()) + } + } + + return string(output) +} + +func extractServerVersion(output string) string { + // Redis response: + // Redis server v=7.2.3 sha=00000000:0 malloc=jemalloc-5.3.0 bits=64 build=7504b1fedf883f2 + // Valkey response: + // Server v=7.2.5 sha=26388270:0 malloc=jemalloc-5.3.0 bits=64 build=ea40bb1576e402d6 + versionSection := strings.Split(output, "v=")[1] + return strings.Split(versionSection, " ")[0] +} + +func TestGlideTestSuite(t *testing.T) { + suite.Run(t, new(GlideTestSuite)) +} + +func (suite *GlideTestSuite) TearDownSuite() { + runClusterManager(suite, []string{"stop", "--prefix", "redis-cluster", "--keep-folder"}, false) +} + +func (suite *GlideTestSuite) TearDownTest() { + for _, client := range suite.clients { + client.Close() + } + + for _, client := range suite.clusterClients { + client.Close() + } +} + +func (suite *GlideTestSuite) runWithDefaultClients(test func(client api.BaseClient)) { + clients := suite.getDefaultClients() + suite.runWithClients(clients, test) +} + +func (suite *GlideTestSuite) getDefaultClients() []api.BaseClient { + return []api.BaseClient{suite.defaultClient(), suite.defaultClusterClient()} +} + +func (suite *GlideTestSuite) defaultClient() *api.GlideClient { + config := api.NewGlideClientConfiguration(). + WithAddress(&api.NodeAddress{Port: suite.standalonePorts[0]}). + WithRequestTimeout(5000) + return suite.client(config) +} + +func (suite *GlideTestSuite) client(config *api.GlideClientConfiguration) *api.GlideClient { + client, err := api.NewGlideClient(config) + + assert.Nil(suite.T(), err) + assert.NotNil(suite.T(), client) + + suite.clients = append(suite.clients, client) + return client +} + +func (suite *GlideTestSuite) defaultClusterClient() *api.GlideClusterClient { + config := api.NewGlideClusterClientConfiguration(). + WithAddress(&api.NodeAddress{Port: suite.clusterPorts[0]}). + WithRequestTimeout(5000) + return suite.clusterClient(config) +} + +func (suite *GlideTestSuite) clusterClient(config *api.GlideClusterClientConfiguration) *api.GlideClusterClient { + client, err := api.NewGlideClusterClient(config) + + assert.Nil(suite.T(), err) + assert.NotNil(suite.T(), client) + + suite.clusterClients = append(suite.clusterClients, client) + return client +} + +func (suite *GlideTestSuite) runWithClients(clients []api.BaseClient, test func(client api.BaseClient)) { + for i, client := range clients { + suite.T().Run(fmt.Sprintf("Testing [%v]", i), func(t *testing.T) { + test(client) + }) + } +} + +func (suite *GlideTestSuite) verifyOK(result string, err error) { + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), api.OK, result) +} diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go new file mode 100644 index 0000000000..793a6fb024 --- /dev/null +++ b/go/integTest/shared_commands_test.go @@ -0,0 +1,172 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package integTest + +import ( + "time" + + "github.com/stretchr/testify/assert" + "github.com/valkey-io/valkey-glide/go/glide/api" +) + +const ( + keyName = "key" + initialValue = "value" + anotherValue = "value2" +) + +func (suite *GlideTestSuite) TestSetAndGet_noOptions() { + suite.runWithDefaultClients(func(client api.BaseClient) { + suite.verifyOK(client.Set(keyName, initialValue)) + result, err := client.Get(keyName) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), initialValue, result) + }) +} + +func (suite *GlideTestSuite) TestSetAndGet_byteString() { + suite.runWithDefaultClients(func(client api.BaseClient) { + invalidUTF8Value := "\xff\xfe\xfd" + suite.verifyOK(client.Set(keyName, invalidUTF8Value)) + result, err := client.Get(keyName) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), invalidUTF8Value, result) + }) +} + +func (suite *GlideTestSuite) TestSetWithOptions_ReturnOldValue() { + suite.runWithDefaultClients(func(client api.BaseClient) { + suite.verifyOK(client.Set(keyName, initialValue)) + + opts := &api.SetOptions{ReturnOldValue: true} + result, err := client.SetWithOptions(keyName, anotherValue, opts) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), initialValue, result) + }) +} + +func (suite *GlideTestSuite) TestSetWithOptions_OnlyIfExists_overwrite() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "TestSetWithOptions_OnlyIfExists_overwrite" + suite.verifyOK(client.Set(key, initialValue)) + + opts := &api.SetOptions{ConditionalSet: api.OnlyIfExists} + suite.verifyOK(client.SetWithOptions(key, anotherValue, opts)) + + result, err := client.Get(key) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), anotherValue, result) + }) +} + +func (suite *GlideTestSuite) TestSetWithOptions_OnlyIfExists_missingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "TestSetWithOptions_OnlyIfExists_missingKey" + opts := &api.SetOptions{ConditionalSet: api.OnlyIfExists} + result, err := client.SetWithOptions(key, anotherValue, opts) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", result) + }) +} + +func (suite *GlideTestSuite) TestSetWithOptions_OnlyIfDoesNotExist_missingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "TestSetWithOptions_OnlyIfDoesNotExist_missingKey" + opts := &api.SetOptions{ConditionalSet: api.OnlyIfDoesNotExist} + suite.verifyOK(client.SetWithOptions(key, anotherValue, opts)) + + result, err := client.Get(key) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), anotherValue, result) + }) +} + +func (suite *GlideTestSuite) TestSetWithOptions_OnlyIfDoesNotExist_existingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "TestSetWithOptions_OnlyIfDoesNotExist_existingKey" + opts := &api.SetOptions{ConditionalSet: api.OnlyIfDoesNotExist} + suite.verifyOK(client.Set(key, initialValue)) + + result, err := client.SetWithOptions(key, anotherValue, opts) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", result) + + result, err = client.Get(key) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), initialValue, result) + }) +} + +func (suite *GlideTestSuite) TestSetWithOptions_KeepExistingExpiry() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "TestSetWithOptions_KeepExistingExpiry" + opts := &api.SetOptions{Expiry: &api.Expiry{Type: api.Milliseconds, Count: uint64(2000)}} + suite.verifyOK(client.SetWithOptions(key, initialValue, opts)) + + result, err := client.Get(key) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), initialValue, result) + + opts = &api.SetOptions{Expiry: &api.Expiry{Type: api.KeepExisting}} + suite.verifyOK(client.SetWithOptions(key, anotherValue, opts)) + + result, err = client.Get(key) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), anotherValue, result) + + time.Sleep(2222 * time.Millisecond) + result, err = client.Get(key) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", result) + }) +} + +func (suite *GlideTestSuite) TestSetWithOptions_UpdateExistingExpiry() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "TestSetWithOptions_UpdateExistingExpiry" + opts := &api.SetOptions{Expiry: &api.Expiry{Type: api.Milliseconds, Count: uint64(100500)}} + suite.verifyOK(client.SetWithOptions(key, initialValue, opts)) + + result, err := client.Get(key) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), initialValue, result) + + opts = &api.SetOptions{Expiry: &api.Expiry{Type: api.Milliseconds, Count: uint64(2000)}} + suite.verifyOK(client.SetWithOptions(key, anotherValue, opts)) + + result, err = client.Get(key) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), anotherValue, result) + + time.Sleep(2222 * time.Millisecond) + result, err = client.Get(key) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", result) + }) +} + +func (suite *GlideTestSuite) TestSetWithOptions_ReturnOldValue_nonExistentKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "TestSetWithOptions_ReturnOldValue_nonExistentKey" + opts := &api.SetOptions{ReturnOldValue: true} + + result, err := client.SetWithOptions(key, anotherValue, opts) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", result) + }) +} diff --git a/go/integTest/standalone_commands_test.go b/go/integTest/standalone_commands_test.go new file mode 100644 index 0000000000..816cdcf89e --- /dev/null +++ b/go/integTest/standalone_commands_test.go @@ -0,0 +1,74 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package integTest + +import ( + "fmt" + "strings" + + "github.com/valkey-io/valkey-glide/go/glide/api" + + "github.com/stretchr/testify/assert" +) + +func (suite *GlideTestSuite) TestCustomCommandInfo() { + client := suite.defaultClient() + result, err := client.CustomCommand([]string{"INFO"}) + + assert.Nil(suite.T(), err) + assert.IsType(suite.T(), "", result) + strResult := result.(string) + assert.True(suite.T(), strings.Contains(strResult, "# Stats")) +} + +func (suite *GlideTestSuite) TestCustomCommandPing() { + client := suite.defaultClient() + result, err := client.CustomCommand([]string{"PING"}) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "PONG", result) +} + +func (suite *GlideTestSuite) TestCustomCommandClientInfo() { + clientName := "TEST_CLIENT_NAME" + config := api.NewGlideClientConfiguration(). + WithAddress(&api.NodeAddress{Port: suite.standalonePorts[0]}). + WithClientName(clientName) + client := suite.client(config) + + result, err := client.CustomCommand([]string{"CLIENT", "INFO"}) + + assert.Nil(suite.T(), err) + assert.IsType(suite.T(), "", result) + strResult := result.(string) + assert.True(suite.T(), strings.Contains(strResult, fmt.Sprintf("name=%s", clientName))) +} + +func (suite *GlideTestSuite) TestCustomCommand_invalidCommand() { + client := suite.defaultClient() + result, err := client.CustomCommand([]string{"pewpew"}) + + assert.Nil(suite.T(), result) + assert.NotNil(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) +} + +func (suite *GlideTestSuite) TestCustomCommand_invalidArgs() { + client := suite.defaultClient() + result, err := client.CustomCommand([]string{"ping", "pang", "pong"}) + + assert.Nil(suite.T(), result) + assert.NotNil(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) +} + +func (suite *GlideTestSuite) TestCustomCommand_closedClient() { + client := suite.defaultClient() + client.Close() + + result, err := client.CustomCommand([]string{"ping"}) + + assert.Nil(suite.T(), result) + assert.NotNil(suite.T(), err) + assert.IsType(suite.T(), &api.ClosingError{}, err) +} diff --git a/go/src/lib.rs b/go/src/lib.rs index bd76ebe347..997b9c9d88 100644 --- a/go/src/lib.rs +++ b/go/src/lib.rs @@ -1,40 +1,73 @@ /* - * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 - */ - -// TODO: Investigate using uniffi bindings for Go instead of cbindgen +* Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 +*/ #![deny(unsafe_op_in_unsafe_fn)] - +use derivative::Derivative; use glide_core::client::Client as GlideClient; use glide_core::connection_request; use glide_core::errors; use glide_core::errors::RequestErrorType; +use glide_core::request_type::RequestType; use glide_core::ConnectionRequest; use protobuf::Message; +use redis::{RedisResult, Value}; +use std::slice::from_raw_parts; use std::{ ffi::{c_void, CString}, - os::raw::c_char, + mem, + os::raw::{c_char, c_double, c_long, c_ulong}, }; use tokio::runtime::Builder; use tokio::runtime::Runtime; -/// Success callback that is called when a Redis command succeeds. +/// The struct represents the response of the command. +/// +/// It will have one of the value populated depending on the return type of the command. +/// +/// The struct is freed by the external caller by using `free_command_response` to avoid memory leaks. +/// TODO: Free the array pointer. +#[repr(C)] +#[derive(Derivative)] +#[derivative(Debug, Default)] +pub struct CommandResponse { + int_value: c_long, + float_value: c_double, + bool_value: bool, + + // Below two values are related to each other. + // `string_value` represents the string. + // `string_value_len` represents the length of the string. + #[derivative(Default(value = "std::ptr::null_mut()"))] + string_value: *mut c_char, + string_value_len: c_long, + + // Below three values are related to each other. + // `array_value` represents the array of strings. + // `array_elements_len` represents the length of each array element. + // `array_value_len` represents the length of the array. + #[derivative(Default(value = "std::ptr::null_mut()"))] + array_value: *mut *mut c_char, + #[derivative(Default(value = "std::ptr::null_mut()"))] + array_elements_len: *mut c_long, + array_value_len: c_long, +} + +/// Success callback that is called when a command succeeds. /// /// The success callback needs to copy the given string synchronously, since it will be dropped by Rust once the callback returns. The callback should be offloaded to a separate thread in order not to exhaust the client's thread pool. /// /// `index_ptr` is a baton-pass back to the caller language to uniquely identify the promise. -/// `message` is the value returned by the Redis command. The 'message' is managed by Rust and is freed when the callback returns control back to the caller. -// TODO: Change message type when implementing command logic -// TODO: Consider using a single response callback instead of success and failure callbacks -pub type SuccessCallback = unsafe extern "C" fn(index_ptr: usize, message: *const c_char) -> (); +/// `message` is the value returned by the command. The 'message' is managed by Rust and is freed when the callback returns control back to the caller. +pub type SuccessCallback = + unsafe extern "C" fn(index_ptr: usize, message: *const CommandResponse) -> (); -/// Failure callback that is called when a Redis command fails. +/// Failure callback that is called when a command fails. /// /// The failure callback needs to copy the given string synchronously, since it will be dropped by Rust once the callback returns. The callback should be offloaded to a separate thread in order not to exhaust the client's thread pool. /// /// `index_ptr` is a baton-pass back to the caller language to uniquely identify the promise. -/// `error_message` is the error message returned by Redis for the failed command. The 'error_message' is managed by Rust and is freed when the callback returns control back to the caller. +/// `error_message` is the error message returned by server for the failed command. The 'error_message' is managed by Rust and is freed when the callback returns control back to the caller. /// `error_type` is the type of error returned by glide-core, depending on the `RedisError` returned. pub type FailureCallback = unsafe extern "C" fn( index_ptr: usize, @@ -74,7 +107,7 @@ fn create_client_internal( let runtime = Builder::new_multi_thread() .enable_all() .worker_threads(1) - .thread_name("GLIDE for Redis Go thread") + .thread_name("Valkey-GLIDE Go thread") .build() .map_err(|err| { let redis_error = err.into(); @@ -97,8 +130,8 @@ fn create_client_internal( /// /// `connection_request_bytes` is an array of bytes that will be parsed into a Protobuf `ConnectionRequest` object. /// `connection_request_len` is the number of bytes in `connection_request_bytes`. -/// `success_callback` is the callback that will be called when a Redis command succeeds. -/// `failure_callback` is the callback that will be called when a Redis command fails. +/// `success_callback` is the callback that will be called when a command succeeds. +/// `failure_callback` is the callback that will be called when a command fails. /// /// # Safety /// @@ -120,7 +153,9 @@ pub unsafe extern "C" fn create_client( let response = match create_client_internal(request_bytes, success_callback, failure_callback) { Err(err) => ConnectionResponse { conn_ptr: std::ptr::null(), - connection_error_message: CString::into_raw(CString::new(err).unwrap()), + connection_error_message: CString::into_raw( + CString::new(err).expect("Couldn't convert error message to CString"), + ), }, Ok(client) => ConnectionResponse { conn_ptr: Box::into_raw(Box::new(client)) as *const c_void, @@ -147,7 +182,7 @@ pub unsafe extern "C" fn create_client( // TODO: Ensure safety when command has not completed yet #[no_mangle] pub unsafe extern "C" fn close_client(client_adapter_ptr: *const c_void) { - assert!(client_adapter_ptr.is_null()); + assert!(!client_adapter_ptr.is_null()); drop(unsafe { Box::from_raw(client_adapter_ptr as *mut ClientAdapter) }); } @@ -170,7 +205,7 @@ pub unsafe extern "C" fn close_client(client_adapter_ptr: *const c_void) { pub unsafe extern "C" fn free_connection_response( connection_response_ptr: *mut ConnectionResponse, ) { - assert!(connection_response_ptr.is_null()); + assert!(!connection_response_ptr.is_null()); let connection_response = unsafe { Box::from_raw(connection_response_ptr) }; let connection_error_message = connection_response.connection_error_message; drop(connection_response); @@ -178,3 +213,184 @@ pub unsafe extern "C" fn free_connection_response( drop(unsafe { CString::from_raw(connection_error_message as *mut c_char) }); } } + +/// Deallocates a `CommandResponse`. +/// +/// This function also frees the contained string_value and array_value. If the string_value and array_value are null pointers, the function returns and only the `CommandResponse` is freed. +/// +/// # Panics +/// +/// This function panics when called with a null `CommandResponse` pointer. +/// +/// # Safety +/// +/// * `free_command_response` can only be called once per `CommandResponse`. Calling it twice is undefined behavior, since the address will be freed twice. +/// * `command_response_ptr` must be obtained from the `CommandResponse` returned in [`SuccessCallback`] from [`command`]. +/// * `command_response_ptr` must be valid until `free_command_response` is called. +/// * The contained `string_value` must be obtained from the `CommandResponse` returned in [`SuccessCallback`] from [`command`]. +/// * The contained `string_value` must be valid until `free_command_response` is called and it must outlive the `CommandResponse` that contains it. +#[no_mangle] +pub unsafe extern "C" fn free_command_response(command_response_ptr: *mut CommandResponse) { + assert!(!command_response_ptr.is_null()); + let command_response = unsafe { Box::from_raw(command_response_ptr) }; + let string_value = command_response.string_value; + let string_value_len = command_response.string_value_len; + drop(command_response); + if !string_value.is_null() { + let len = string_value_len as usize; + unsafe { Vec::from_raw_parts(string_value, len, len) }; + } +} + +/// Frees the error_message received on a command failure. +/// +/// # Panics +/// +/// This functions panics when called with a null `c_char` pointer. +/// +/// # Safety +/// +/// `free_error_message` can only be called once per `error_message`. Calling it twice is undefined +/// behavior, since the address will be freed twice. +#[no_mangle] +pub unsafe extern "C" fn free_error_message(error_message: *mut c_char) { + assert!(!error_message.is_null()); + drop(unsafe { CString::from_raw(error_message as *mut c_char) }); +} + +/// Converts a double pointer to a vec. +/// +/// # Safety +/// +/// `convert_double_pointer_to_vec` returns a `Vec` of u8 slice which holds pointers of `go` +/// strings. The returned `Vec<&'a [u8]>` is meant to be copied into Rust code. Storing them +/// for later use will cause the program to crash as the pointers will be freed by go's gc +unsafe fn convert_double_pointer_to_vec<'a>( + data: *const *const c_void, + len: c_ulong, + data_len: *const c_ulong, +) -> Vec<&'a [u8]> { + let string_ptrs = unsafe { from_raw_parts(data, len as usize) }; + let string_lengths = unsafe { from_raw_parts(data_len, len as usize) }; + let mut result = Vec::<&[u8]>::with_capacity(string_ptrs.len()); + for (i, &str_ptr) in string_ptrs.iter().enumerate() { + let slice = unsafe { from_raw_parts(str_ptr as *const u8, string_lengths[i] as usize) }; + result.push(slice); + } + result +} + +fn convert_vec_to_pointer(mut vec: Vec) -> (*mut T, c_long) { + vec.shrink_to_fit(); + let vec_ptr = vec.as_mut_ptr(); + let len = vec.len() as c_long; + mem::forget(vec); + (vec_ptr, len) +} + +// TODO: Finish documentation +/// Executes a command. +/// +/// # Safety +/// +/// * TODO: finish safety section. +#[no_mangle] +pub unsafe extern "C" fn command( + client_adapter_ptr: *const c_void, + channel: usize, + command_type: RequestType, + arg_count: c_ulong, + args: *const usize, + args_len: *const c_ulong, +) { + let client_adapter = + unsafe { Box::leak(Box::from_raw(client_adapter_ptr as *mut ClientAdapter)) }; + // The safety of this needs to be ensured by the calling code. Cannot dispose of the pointer before + // all operations have completed. + let ptr_address = client_adapter_ptr as usize; + + let arg_vec = + unsafe { convert_double_pointer_to_vec(args as *const *const c_void, arg_count, args_len) }; + + let mut client_clone = client_adapter.client.clone(); + + // Create the command outside of the task to ensure that the command arguments passed + // from "go" are still valid + let mut cmd = command_type + .get_command() + .expect("Couldn't fetch command type"); + for command_arg in arg_vec { + cmd.arg(command_arg); + } + + client_adapter.runtime.spawn(async move { + let result = client_clone.send_command(&cmd, None).await; + let client_adapter = unsafe { Box::leak(Box::from_raw(ptr_address as *mut ClientAdapter)) }; + let value = match result { + Ok(value) => value, + Err(err) => { + let message = errors::error_message(&err); + let error_type = errors::error_type(&err); + + let c_err_str = CString::into_raw( + CString::new(message).expect("Couldn't convert error message to CString"), + ); + unsafe { (client_adapter.failure_callback)(channel, c_err_str, error_type) }; + return; + } + }; + + let mut command_response = CommandResponse::default(); + let result: RedisResult> = match value { + Value::Nil => Ok(None), + Value::SimpleString(text) => { + let vec = text.chars().map(|b| b as c_char).collect::>(); + let (vec_ptr, len) = convert_vec_to_pointer(vec); + command_response.string_value = vec_ptr; + command_response.string_value_len = len; + Ok(Some(command_response)) + } + Value::BulkString(text) => { + let vec = text.iter().map(|b| *b as c_char).collect::>(); + let (vec_ptr, len) = convert_vec_to_pointer(vec); + command_response.string_value = vec_ptr; + command_response.string_value_len = len; + Ok(Some(command_response)) + } + Value::VerbatimString { format: _, text } => { + let vec = text.chars().map(|b| b as c_char).collect::>(); + let (vec_ptr, len) = convert_vec_to_pointer(vec); + command_response.string_value = vec_ptr; + command_response.string_value_len = len; + Ok(Some(command_response)) + } + Value::Okay => { + let vec = "OK".chars().map(|b| b as c_char).collect::>(); + let (vec_ptr, len) = convert_vec_to_pointer(vec); + command_response.string_value = vec_ptr; + command_response.string_value_len = len; + Ok(Some(command_response)) + } + // TODO: Add support for other return types. + _ => todo!(), + }; + + unsafe { + match result { + Ok(None) => (client_adapter.success_callback)(channel, std::ptr::null()), + Ok(Some(message)) => { + (client_adapter.success_callback)(channel, Box::into_raw(Box::new(message))) + } + Err(err) => { + let message = errors::error_message(&err); + let error_type = errors::error_type(&err); + + let c_err_str = CString::into_raw( + CString::new(message).expect("Couldn't convert error message to CString"), + ); + (client_adapter.failure_callback)(channel, c_err_str, error_type); + } + }; + } + }); +} diff --git a/java/.ort.yml b/java/.ort.yml index fa5555e99e..ace1740f01 100644 --- a/java/.ort.yml +++ b/java/.ort.yml @@ -11,10 +11,13 @@ excludes: scopes: - pattern: "test.*" reason: "TEST_DEPENDENCY_OF" - comment: Packages for testing only. Not part of released artifacts." + comment: Packages for testing only. Not part of released artifacts. - pattern: "(spotbugs.*|spotbugsSlf4j.*)" reason: "TEST_DEPENDENCY_OF" - comment: Packages for static analysis only. Not part of released artifacts." + comment: Packages for static analysis only. Not part of released artifacts. - pattern: "jacoco.*" reason: "TEST_DEPENDENCY_OF" - comment: Packages for code coverage verification only. Not part of released artifacts." + comment: Packages for code coverage verification only. Not part of released artifacts. + - pattern: "compileClasspath.*" + reason: "TEST_DEPENDENCY_OF" + comment: Packages for Gradle only. Not part of released artifacts. diff --git a/java/Cargo.toml b/java/Cargo.toml index 452b80856c..7c5e6798f5 100644 --- a/java/Cargo.toml +++ b/java/Cargo.toml @@ -22,3 +22,6 @@ bytes = { version = "1.6.0" } [profile.release] lto = true debug = true + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ffi_test)'] } diff --git a/java/DEVELOPER.md b/java/DEVELOPER.md index d4dfd0ea82..bd04f278af 100644 --- a/java/DEVELOPER.md +++ b/java/DEVELOPER.md @@ -84,8 +84,7 @@ Before starting this step, make sure you've installed all software dependencies. 1. Clone the repository: ```bash - VERSION=0.1.0 # You can modify this to other released version or set it to "main" to get the unstable branch - git clone --branch ${VERSION} https://github.com/valkey-io/valkey-glide.git + git clone https://github.com/valkey-io/valkey-glide.git cd valkey-glide/java ``` 2. Initialize git submodule: @@ -189,11 +188,10 @@ dependencies { } ``` -Optionally: you can specify a snapshot release and classifier: +Optionally: you can specify a snapshot release: ```bash -export GLIDE_LOCAL_VERSION=1.0.0-SNAPSHOT -export GLIDE_LOCAL_CLASSIFIER=osx-aarch_64 +export GLIDE_RELEASE_VERSION=1.0.1-SNAPSHOT ./gradlew publishToMavenLocal ``` @@ -204,7 +202,7 @@ repositories { } dependencies { // Update to use version defined in the previous step - implementation group: 'io.valkey', name: 'valkey-glide', version: '1.0.0-SNAPSHOT', classifier='osx-aarch_64' + implementation group: 'io.valkey', name: 'valkey-glide', version: '1.0.1-SNAPSHOT', classifier='osx-aarch_64' } ``` diff --git a/java/README.md b/java/README.md index aaa29dd0be..9b14dab87d 100644 --- a/java/README.md +++ b/java/README.md @@ -66,22 +66,22 @@ Gradle: ```groovy // osx-aarch_64 dependencies { - implementation group: 'io.valkey', name: 'valkey-glide', version: '1.0.1', classifier: 'osx-aarch_64' + implementation group: 'io.valkey', name: 'valkey-glide', version: '1.+', classifier: 'osx-aarch_64' } // osx-x86_64 dependencies { - implementation group: 'io.valkey', name: 'valkey-glide', version: '1.0.1', classifier: 'osx-x86_64' + implementation group: 'io.valkey', name: 'valkey-glide', version: '1.+', classifier: 'osx-x86_64' } // linux-aarch_64 dependencies { - implementation group: 'io.valkey', name: 'valkey-glide', version: '1.0.1', classifier: 'linux-aarch_64' + implementation group: 'io.valkey', name: 'valkey-glide', version: '1.+', classifier: 'linux-aarch_64' } // linux-x86_64 dependencies { - implementation group: 'io.valkey', name: 'valkey-glide', version: '1.0.1', classifier: 'linux-x86_64' + implementation group: 'io.valkey', name: 'valkey-glide', version: '1.+', classifier: 'linux-x86_64' } // with osdetector @@ -89,7 +89,7 @@ plugins { id "com.google.osdetector" version "1.7.3" } dependencies { - implementation group: 'io.valkey', name: 'valkey-glide', version: '1.0.1', classifier: osdetector.classifier + implementation group: 'io.valkey', name: 'valkey-glide', version: '1.+', classifier: osdetector.classifier } ``` @@ -102,7 +102,7 @@ Maven: io.valkey valkey-glide osx-aarch_64 - 1.0.1 + [1.0.0,2.0.0) @@ -110,7 +110,7 @@ Maven: io.valkey valkey-glide osx-x86_64 - 1.0.1 + [1.0.0,2.0.0) @@ -118,7 +118,7 @@ Maven: io.valkey valkey-glide linux-aarch_64 - 1.0.1 + [1.0.0,2.0.0) @@ -126,10 +126,26 @@ Maven: io.valkey valkey-glide linux-x86_64 - 1.0.1 + [1.0.0,2.0.0) ``` +SBT: +- **IMPORTANT** must include a `classifier`. Please use this dependency block and add it to the build.sbt file. +```scala +// osx-aarch_64 +libraryDependencies += "io.valkey" % "valkey-glide" % "1.+" classifier "osx-aarch_64" + +// osx-x86_64 +libraryDependencies += "io.valkey" % "valkey-glide" % "1.+" classifier "osx-x86_64" + +// linux-aarch_64 +libraryDependencies += "io.valkey" % "valkey-glide" % "1.+" classifier "linux-aarch_64" + +// linux-x86_64 +libraryDependencies += "io.valkey" % "valkey-glide" % "1.+" classifier "linux-x86_64" +``` + ## Setting up the Java module To use Valkey GLIDE in a Java project with modules, include a module-info.java in your project. @@ -245,6 +261,9 @@ public class Main { } ``` +### Scala and Kotlin Examples +See [our Scala and Kotlin examples](../examples/) to learn how to use Valkey GLIDE in Scala and Kotlin projects. + ### Accessing tests For more examples, you can refer to the test folder [unit tests](./client/src/test/java/glide/api) and [integration tests](./integTest/src/test/java/glide). @@ -252,11 +271,21 @@ For more examples, you can refer to the test folder [unit tests](./client/src/te You can run benchmarks using `./gradlew run`. You can set arguments using the args flag like: +Returns the command help output ```shell ./gradlew run --args="--help" +``` + +Runs all benchmark clients against a local instance with TLS enabled using data sizing 100 and 1000 bytes, 10 and 100 concurrent tasks, 1 and 5 parallel clients. +```shell ./gradlew run --args="--resultsFile=output --dataSize \"100 1000\" --concurrentTasks \"10 100\" --clients all --host localhost --port 6279 --clientCount \"1 5\" --tls" ``` +Runs GLIDE client against a local cluster instance on port 52756 using data sizing 4000 bytes, and 1000 concurrent tasks. +```shell +./gradlew run --args="--resultsFile=output --dataSize \"4000\" --concurrentTasks \"1000\" --clients glide --host 127.0.0.1 --port 52746 --clusterModeEnabled" +``` + The following arguments are accepted: * `resultsFile`: the results output file * `concurrentTasks`: number of concurrent tasks diff --git a/java/THIRD_PARTY_LICENSES_JAVA b/java/THIRD_PARTY_LICENSES_JAVA index a363964613..fc65035b85 100644 --- a/java/THIRD_PARTY_LICENSES_JAVA +++ b/java/THIRD_PARTY_LICENSES_JAVA @@ -683,10 +683,23 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ahash:0.8.11 +Package: adler2:2.0.0 The following copyrights and licenses were found in the source code of this package: +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + -- + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -912,7 +925,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: allocator-api2:0.2.18 +Package: ahash:0.8.11 The following copyrights and licenses were found in the source code of this package: @@ -1141,7 +1154,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: android-tzdata:0.1.1 +Package: allocator-api2:0.2.18 The following copyrights and licenses were found in the source code of this package: @@ -1370,7 +1383,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: android_system_properties:0.1.5 +Package: android-tzdata:0.1.1 The following copyrights and licenses were found in the source code of this package: @@ -1599,7 +1612,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: arc-swap:1.7.1 +Package: android_system_properties:0.1.5 The following copyrights and licenses were found in the source code of this package: @@ -1828,7 +1841,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: arcstr:1.2.0 +Package: arc-swap:1.7.1 The following copyrights and licenses were found in the source code of this package: @@ -2055,29 +2068,9 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. - -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. - ---- -Package: async-trait:0.1.81 +Package: arcstr:1.2.0 The following copyrights and licenses were found in the source code of this package: @@ -2304,9 +2297,29 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -- + +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + ---- -Package: backoff:0.4.0 +Package: async-trait:0.1.81 The following copyrights and licenses were found in the source code of this package: @@ -2535,7 +2548,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: backtrace:0.3.73 +Package: backoff:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -2764,7 +2777,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: base64:0.22.1 +Package: backtrace:0.3.73 The following copyrights and licenses were found in the source code of this package: @@ -2993,7 +3006,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bitflags:2.6.0 +Package: base64:0.22.1 The following copyrights and licenses were found in the source code of this package: @@ -3222,7 +3235,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bumpalo:3.16.0 +Package: bitflags:2.6.0 The following copyrights and licenses were found in the source code of this package: @@ -3451,32 +3464,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bytes:1.6.1 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: cesu8:1.1.0 +Package: bumpalo:3.16.0 The following copyrights and licenses were found in the source code of this package: @@ -3705,7 +3693,84 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: cfg-if:1.0.0 +Package: byteorder:1.5.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -- + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +---- + +Package: bytes:1.7.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: cesu8:1.1.0 The following copyrights and licenses were found in the source code of this package: @@ -3934,7 +3999,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: chrono:0.4.38 +Package: cfg-if:1.0.0 The following copyrights and licenses were found in the source code of this package: @@ -4163,32 +4228,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: combine:4.6.7 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: core-foundation:0.9.4 +Package: chrono:0.4.38 The following copyrights and licenses were found in the source code of this package: @@ -4417,7 +4457,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: core-foundation-sys:0.8.6 +Package: combine:4.6.7 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: core-foundation:0.9.4 The following copyrights and licenses were found in the source code of this package: @@ -4646,32 +4711,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crc16:0.4.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: crc32fast:1.4.2 +Package: core-foundation-sys:0.8.7 The following copyrights and licenses were found in the source code of this package: @@ -4900,7 +4940,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crossbeam-channel:0.5.13 +Package: crc16:0.4.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: crc32fast:1.4.2 The following copyrights and licenses were found in the source code of this package: @@ -5129,7 +5194,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crossbeam-utils:0.8.20 +Package: crossbeam-channel:0.5.13 The following copyrights and licenses were found in the source code of this package: @@ -5358,7 +5423,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: deranged:0.3.11 +Package: crossbeam-utils:0.8.20 The following copyrights and licenses were found in the source code of this package: @@ -5587,7 +5652,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: derivative:2.2.0 +Package: dashmap:6.0.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: deranged:0.3.11 The following copyrights and licenses were found in the source code of this package: @@ -5816,7 +5906,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: directories:4.0.1 +Package: derivative:2.2.0 The following copyrights and licenses were found in the source code of this package: @@ -6045,7 +6135,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: dirs-sys:0.3.7 +Package: directories:4.0.1 The following copyrights and licenses were found in the source code of this package: @@ -6274,7 +6364,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: dispose:0.5.0 +Package: dirs-sys:0.3.7 The following copyrights and licenses were found in the source code of this package: @@ -6503,7 +6593,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: dispose-derive:0.4.0 +Package: dispose:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -6732,7 +6822,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: fast-math:0.1.1 +Package: dispose-derive:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -6961,32 +7051,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: file-rotate:0.7.6 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: flate2:1.0.30 +Package: fast-math:0.1.1 The following copyrights and licenses were found in the source code of this package: @@ -7215,7 +7280,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: form_urlencoded:1.2.1 +Package: file-rotate:0.7.6 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: flate2:1.0.33 The following copyrights and licenses were found in the source code of this package: @@ -7444,7 +7534,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures:0.3.30 +Package: form_urlencoded:1.2.1 The following copyrights and licenses were found in the source code of this package: @@ -7673,7 +7763,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-channel:0.3.30 +Package: futures:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -7902,7 +7992,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-core:0.3.30 +Package: futures-channel:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -8131,7 +8221,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-executor:0.3.30 +Package: futures-core:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -8360,7 +8450,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-intrusive:0.5.0 +Package: futures-executor:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -8589,7 +8679,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-io:0.3.30 +Package: futures-intrusive:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -8818,7 +8908,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-macro:0.3.30 +Package: futures-io:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9047,7 +9137,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-sink:0.3.30 +Package: futures-macro:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9276,7 +9366,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-task:0.3.30 +Package: futures-sink:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9505,7 +9595,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-util:0.3.30 +Package: futures-task:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9734,7 +9824,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: getrandom:0.2.15 +Package: futures-util:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9963,7 +10053,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: gimli:0.29.0 +Package: getrandom:0.2.15 The following copyrights and licenses were found in the source code of this package: @@ -10192,7 +10282,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: glide-core:0.1.0 +Package: gimli:0.29.0 The following copyrights and licenses were found in the source code of this package: @@ -10398,9 +10488,30 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. + -- + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ---- -Package: hashbrown:0.14.5 +Package: glide-core:0.1.0 The following copyrights and licenses were found in the source code of this package: @@ -10606,30 +10717,9 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- -Package: heck:0.5.0 +Package: hashbrown:0.14.5 The following copyrights and licenses were found in the source code of this package: @@ -10858,7 +10948,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: hermit-abi:0.3.9 +Package: heck:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -11087,7 +11177,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: iana-time-zone:0.1.60 +Package: hermit-abi:0.3.9 The following copyrights and licenses were found in the source code of this package: @@ -11316,7 +11406,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: iana-time-zone-haiku:0.1.2 +Package: iana-time-zone:0.1.60 The following copyrights and licenses were found in the source code of this package: @@ -11545,7 +11635,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: idna:0.5.0 +Package: iana-time-zone-haiku:0.1.2 The following copyrights and licenses were found in the source code of this package: @@ -11774,7 +11864,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ieee754:0.2.6 +Package: idna:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -12003,63 +12093,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: instant:0.1.13 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: integer-encoding:4.0.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: itoa:1.0.11 +Package: ieee754:0.2.6 The following copyrights and licenses were found in the source code of this package: @@ -12288,7 +12322,63 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: jni:0.21.1 +Package: instant:0.1.13 + +The following copyrights and licenses were found in the source code of this package: + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + +Package: integer-encoding:4.0.2 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: itoa:1.0.11 The following copyrights and licenses were found in the source code of this package: @@ -12517,7 +12607,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: jni-sys:0.3.0 +Package: jni:0.21.1 The following copyrights and licenses were found in the source code of this package: @@ -12746,7 +12836,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: js-sys:0.3.69 +Package: jni-sys:0.3.0 The following copyrights and licenses were found in the source code of this package: @@ -12975,7 +13065,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: lazy_static:1.5.0 +Package: js-sys:0.3.70 The following copyrights and licenses were found in the source code of this package: @@ -13204,7 +13294,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: libc:0.2.155 +Package: lazy_static:1.5.0 The following copyrights and licenses were found in the source code of this package: @@ -13433,7 +13523,236 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: libredox:0.1.3 +Package: libc:0.2.158 + +The following copyrights and licenses were found in the source code of this package: + + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + -- + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: libredox:0.1.3 The following copyrights and licenses were found in the source code of this package: @@ -14425,82 +14744,7 @@ the following restrictions: ---- -Package: mio:0.8.11 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: nanoid:0.4.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: nu-ansi-term:0.46.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: num-bigint:0.4.6 +Package: miniz_oxide:0.8.0 The following copyrights and licenses were found in the source code of this package: @@ -14727,9 +14971,104 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -- + +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + ---- -Package: num-conv:0.1.0 +Package: mio:1.0.2 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: nanoid:0.4.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: nu-ansi-term:0.46.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: num-bigint:0.4.6 The following copyrights and licenses were found in the source code of this package: @@ -14958,7 +15297,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: num-integer:0.1.46 +Package: num-conv:0.1.0 The following copyrights and licenses were found in the source code of this package: @@ -15187,7 +15526,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: num-traits:0.2.19 +Package: num-integer:0.1.46 The following copyrights and licenses were found in the source code of this package: @@ -15416,7 +15755,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: num_cpus:1.16.0 +Package: num-traits:0.2.19 The following copyrights and licenses were found in the source code of this package: @@ -15645,7 +15984,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: object:0.36.1 +Package: num_cpus:1.16.0 The following copyrights and licenses were found in the source code of this package: @@ -15874,7 +16213,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: once_cell:1.19.0 +Package: object:0.36.3 The following copyrights and licenses were found in the source code of this package: @@ -16103,7 +16442,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: openssl-probe:0.1.5 +Package: once_cell:1.19.0 The following copyrights and licenses were found in the source code of this package: @@ -16332,32 +16671,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: overload:0.1.1 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: parking_lot:0.12.3 +Package: openssl-probe:0.1.5 The following copyrights and licenses were found in the source code of this package: @@ -16586,7 +16900,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: parking_lot_core:0.9.10 +Package: overload:0.1.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: parking_lot:0.12.3 The following copyrights and licenses were found in the source code of this package: @@ -16815,7 +17154,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: percent-encoding:2.3.1 +Package: parking_lot_core:0.9.10 The following copyrights and licenses were found in the source code of this package: @@ -17044,7 +17383,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project:1.1.5 +Package: percent-encoding:2.3.1 The following copyrights and licenses were found in the source code of this package: @@ -17273,7 +17612,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-internal:1.1.5 +Package: pin-project:1.1.5 The following copyrights and licenses were found in the source code of this package: @@ -17502,7 +17841,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-lite:0.2.14 +Package: pin-project-internal:1.1.5 The following copyrights and licenses were found in the source code of this package: @@ -17731,7 +18070,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-utils:0.1.0 +Package: pin-project-lite:0.2.14 The following copyrights and licenses were found in the source code of this package: @@ -17960,7 +18299,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: powerfmt:0.2.0 +Package: pin-utils:0.1.0 The following copyrights and licenses were found in the source code of this package: @@ -18189,7 +18528,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ppv-lite86:0.2.17 +Package: powerfmt:0.2.0 The following copyrights and licenses were found in the source code of this package: @@ -18418,7 +18757,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro-error:1.0.4 +Package: ppv-lite86:0.2.20 The following copyrights and licenses were found in the source code of this package: @@ -18647,7 +18986,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro-error-attr:1.0.4 +Package: proc-macro-error:1.0.4 The following copyrights and licenses were found in the source code of this package: @@ -18876,7 +19215,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro2:1.0.86 +Package: proc-macro-error-attr:1.0.4 The following copyrights and licenses were found in the source code of this package: @@ -19105,57 +19444,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: protobuf:3.5.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: protobuf-support:3.5.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: quote:1.0.36 +Package: proc-macro2:1.0.86 The following copyrights and licenses were found in the source code of this package: @@ -19384,214 +19673,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand:0.8.5 +Package: protobuf:3.5.1 The following copyrights and licenses were found in the source code of this package: - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -19613,7 +19698,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand_chacha:0.3.1 +Package: protobuf-support:3.5.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: quote:1.0.37 The following copyrights and licenses were found in the source code of this package: @@ -19842,7 +19952,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand_core:0.6.4 +Package: rand:0.8.5 The following copyrights and licenses were found in the source code of this package: @@ -20071,65 +20181,213 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: redis:0.25.2 +Package: rand_chacha:0.3.1 The following copyrights and licenses were found in the source code of this package: -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 1. Definitions. -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + "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. -Package: redox_syscall:0.5.3 + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. -The following copyrights and licenses were found in the source code of this package: + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: + "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. -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. + "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). -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + "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." -Package: redox_users:0.4.5 + "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. -The following copyrights and licenses were found in the source code of this package: + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -20152,7 +20410,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustc-demangle:0.1.24 +Package: rand_core:0.6.4 The following copyrights and licenses were found in the source code of this package: @@ -20381,227 +20639,65 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls:0.22.4 +Package: redis:0.25.2 The following copyrights and licenses were found in the source code of this package: - 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. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: - 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. +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. - 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. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. - END OF TERMS AND CONDITIONS +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. - APPENDIX: How to apply the Apache License to your work. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. +---- - Copyright [yyyy] [name of copyright owner] +Package: redox_syscall:0.5.3 - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +The following copyrights and licenses were found in the source code of this package: - http://www.apache.org/licenses/LICENSE-2.0 +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - -- +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. +---- -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. +Package: redox_users:0.4.6 - -- +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -20624,7 +20720,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-native-certs:0.7.1 +Package: rustc-demangle:0.1.24 The following copyrights and licenses were found in the source code of this package: @@ -20832,20 +20928,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - - -- - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -20867,7 +20949,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pemfile:2.1.2 +Package: rustls:0.22.4 The following copyrights and licenses were found in the source code of this package: @@ -21110,7 +21192,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pki-types:1.7.0 +Package: rustls-native-certs:0.7.2 The following copyrights and licenses were found in the source code of this package: @@ -21318,6 +21400,20 @@ The following copyrights and licenses were found in the source code of this pack -- +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + -- + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -21339,25 +21435,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-webpki:0.102.5 - -The following copyrights and licenses were found in the source code of this package: - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - ----- - -Package: rustversion:1.0.17 +Package: rustls-pemfile:2.1.3 The following copyrights and licenses were found in the source code of this package: @@ -21565,6 +21643,20 @@ The following copyrights and licenses were found in the source code of this pack -- +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + -- + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -21586,7 +21678,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ryu:1.0.18 +Package: rustls-pki-types:1.8.0 The following copyrights and licenses were found in the source code of this package: @@ -21794,36 +21886,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - ----- - -Package: schannel:0.1.23 - -The following copyrights and licenses were found in the source code of this package: - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -21845,7 +21907,25 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: scopeguard:1.2.0 +Package: rustls-webpki:0.102.6 + +The following copyrights and licenses were found in the source code of this package: + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +---- + +Package: rustversion:1.0.17 The following copyrights and licenses were found in the source code of this package: @@ -22074,7 +22154,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework:2.11.1 +Package: ryu:1.0.18 The following copyrights and licenses were found in the source code of this package: @@ -22282,6 +22362,36 @@ The following copyrights and licenses were found in the source code of this pack -- +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +---- + +Package: schannel:0.1.23 + +The following copyrights and licenses were found in the source code of this package: + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -22303,7 +22413,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework-sys:2.11.1 +Package: scopeguard:1.2.0 The following copyrights and licenses were found in the source code of this package: @@ -22532,7 +22642,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde:1.0.204 +Package: security-framework:2.11.1 The following copyrights and licenses were found in the source code of this package: @@ -22761,7 +22871,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde_derive:1.0.204 +Package: security-framework-sys:2.11.1 The following copyrights and licenses were found in the source code of this package: @@ -22990,88 +23100,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: sha1_smol:1.0.0 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: sharded-slab:0.1.7 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: slab:0.4.9 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: smallvec:1.13.2 +Package: serde:1.0.209 The following copyrights and licenses were found in the source code of this package: @@ -23300,7 +23329,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: socket2:0.5.7 +Package: serde_derive:1.0.209 The following copyrights and licenses were found in the source code of this package: @@ -23529,32 +23558,38 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: spin:0.9.8 +Package: sha1_smol:1.0.1 The following copyrights and licenses were found in the source code of this package: -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -Package: strum:0.26.3 +Package: sharded-slab:0.1.7 The following copyrights and licenses were found in the source code of this package: @@ -23579,7 +23614,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: strum_macros:0.26.4 +Package: slab:0.4.9 The following copyrights and licenses were found in the source code of this package: @@ -23604,38 +23639,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: subtle:2.6.1 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: syn:1.0.109 +Package: smallvec:1.13.2 The following copyrights and licenses were found in the source code of this package: @@ -23864,7 +23868,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.71 +Package: socket2:0.5.7 The following copyrights and licenses were found in the source code of this package: @@ -24093,213 +24097,59 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: thiserror:1.0.62 +Package: spin:0.9.8 The following copyrights and licenses were found in the source code of this package: - 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. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - 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. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - 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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - 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 +Package: strum:0.26.3 - APPENDIX: How to apply the Apache License to your work. +The following copyrights and licenses were found in the source code of this package: - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - Copyright [yyyy] [name of copyright owner] +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - http://www.apache.org/licenses/LICENSE-2.0 +---- - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Package: strum_macros:0.26.4 - -- +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -24322,236 +24172,38 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: thiserror-impl:1.0.62 +Package: subtle:2.6.1 The following copyrights and licenses were found in the source code of this package: - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: - -- +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -Package: thread_local:1.1.8 +Package: syn:1.0.109 The following copyrights and licenses were found in the source code of this package: @@ -24780,7 +24432,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: time:0.3.36 +Package: syn:2.0.76 The following copyrights and licenses were found in the source code of this package: @@ -25009,7 +24661,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: time-core:0.1.2 +Package: thiserror:1.0.63 The following copyrights and licenses were found in the source code of this package: @@ -25238,7 +24890,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: time-macros:0.2.18 +Package: thiserror-impl:1.0.63 The following copyrights and licenses were found in the source code of this package: @@ -25467,7 +25119,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: tinyvec:1.8.0 +Package: thread_local:1.1.8 The following copyrights and licenses were found in the source code of this package: @@ -25694,29 +25346,9 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. - -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. - ---- -Package: tinyvec_macros:0.1.1 +Package: time:0.3.36 The following copyrights and licenses were found in the source code of this package: @@ -25943,104 +25575,9 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. - -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. - ---- -Package: tokio:1.38.1 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tokio-macros:2.3.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tokio-retry:0.3.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tokio-rustls:0.25.0 +Package: time-core:0.1.2 The following copyrights and licenses were found in the source code of this package: @@ -26269,182 +25806,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: tokio-util:0.7.11 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing:0.1.40 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-appender:0.2.3 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-attributes:0.1.27 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-core:0.1.32 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-log:0.2.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-subscriber:0.3.18 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: unicode-bidi:0.3.15 +Package: time-macros:0.2.18 The following copyrights and licenses were found in the source code of this package: @@ -26673,7 +26035,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: unicode-ident:1.0.12 +Package: tinyvec:1.8.0 The following copyrights and licenses were found in the source code of this package: @@ -26902,68 +26264,27 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -- -UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE - -Unicode Data Files include all data files under the directories -http://www.unicode.org/Public/, http://www.unicode.org/reports/, -http://www.unicode.org/cldr/data/, http://source.icu- -project.org/repos/icu/, and -http://www.unicode.org/utility/trac/browser/. - -Unicode Data Files do not include PDF online code charts under the -directory http://www.unicode.org/Public/. - -Software includes any source code published in the Unicode Standard or -under the directories http://www.unicode.org/Public/, -http://www.unicode.org/reports/, http://www.unicode.org/cldr/data/, -http://source.icu-project.org/repos/icu/, and -http://www.unicode.org/utility/trac/browser/. - -NOTICE TO USER: Carefully read the following legal agreement. BY -DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA -FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY -ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF -THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, -DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. - -COPYRIGHT AND PERMISSION NOTICE - -Copyright © 1991-2016 Unicode, Inc. All rights reserved. Distributed -under the Terms of Use in http://www.unicode.org/copyright.html. - -Permission is hereby granted, free of charge, to any person obtaining a -copy of the Unicode data files and any associated documentation (the -"Data Files") or Unicode software and any associated documentation (the -"Software") to deal in the Data Files or Software without restriction, -including without limitation the rights to use, copy, modify, merge, -publish, distribute, and/or sell copies of the Data Files or Software, -and to permit persons to whom the Data Files or Software are furnished -to do so, provided that either +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. -(a) this copyright and permission notice appear with all copies of the -Data Files or Software, or +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: -(b) this copyright and permission notice appear in associated -Documentation. +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. -THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR -ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER -RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF -CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. -Except as contained in this notice, the name of a copyright holder shall -not be used in advertising or otherwise to promote the sale, use or -other dealings in these Data Files or Software without prior written -authorization of the copyright holder. +3. This notice may not be removed or altered from any source distribution. ---- -Package: unicode-normalization:0.1.23 +Package: tinyvec_macros:0.1.1 The following copyrights and licenses were found in the source code of this package: @@ -27190,234 +26511,32 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----- + -- -Package: untrusted:0.9.0 +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. -The following copyrights and licenses were found in the source code of this package: +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. ---- -Package: url:2.5.0 +Package: tokio:1.39.3 The following copyrights and licenses were found in the source code of this package: - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -27439,7 +26558,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: valuable:0.1.0 +Package: tokio-macros:2.4.0 The following copyrights and licenses were found in the source code of this package: @@ -27464,434 +26583,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasi:0.11.0+wasi-snapshot-preview1 +Package: tokio-retry:0.3.0 The following copyrights and licenses were found in the source code of this package: - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ---- LLVM Exceptions to the Apache 2.0 License ---- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. - - -- - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -27913,7 +26608,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen:0.2.92 +Package: tokio-rustls:0.25.0 The following copyrights and licenses were found in the source code of this package: @@ -28142,7 +26837,182 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-backend:0.2.92 +Package: tokio-util:0.7.11 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing:0.1.40 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing-appender:0.2.3 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing-attributes:0.1.27 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing-core:0.1.32 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing-log:0.2.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing-subscriber:0.3.18 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: unicode-bidi:0.3.15 The following copyrights and licenses were found in the source code of this package: @@ -28371,7 +27241,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro:0.2.92 +Package: unicode-ident:1.0.12 The following copyrights and licenses were found in the source code of this package: @@ -28598,9 +27468,70 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -- + +UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE + +Unicode Data Files include all data files under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu- +project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +Unicode Data Files do not include PDF online code charts under the +directory http://www.unicode.org/Public/. + +Software includes any source code published in the Unicode Standard or +under the directories http://www.unicode.org/Public/, +http://www.unicode.org/reports/, http://www.unicode.org/cldr/data/, +http://source.icu-project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA +FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY +ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF +THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, +DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2016 Unicode, Inc. All rights reserved. Distributed +under the Terms of Use in http://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of the Unicode data files and any associated documentation (the +"Data Files") or Unicode software and any associated documentation (the +"Software") to deal in the Data Files or Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, and/or sell copies of the Data Files or Software, +and to permit persons to whom the Data Files or Software are furnished +to do so, provided that either + +(a) this copyright and permission notice appear with all copies of the +Data Files or Software, or + +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR +ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or +other dealings in these Data Files or Software without prior written +authorization of the copyright holder. + ---- -Package: wasm-bindgen-macro-support:0.2.92 +Package: unicode-normalization:0.1.23 The following copyrights and licenses were found in the source code of this package: @@ -28829,7 +27760,25 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-shared:0.2.92 +Package: untrusted:0.9.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +---- + +Package: url:2.5.0 The following copyrights and licenses were found in the source code of this package: @@ -29058,7 +28007,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: winapi:0.3.9 +Package: valuable:0.1.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: wasi:0.11.0+wasi-snapshot-preview1 The following copyrights and licenses were found in the source code of this package: @@ -29266,31 +28240,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: winapi-i686-pc-windows-gnu:0.4.0 - -The following copyrights and licenses were found in the source code of this package: - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -29493,234 +28442,21 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: winapi-x86_64-pc-windows-gnu:0.4.0 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +--- LLVM Exceptions to the Apache 2.0 License ---- - http://www.apache.org/licenses/LICENSE-2.0 +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. -- @@ -29745,7 +28481,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-core:0.52.0 +Package: wasm-bindgen:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -29974,7 +28710,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-sys:0.45.0 +Package: wasm-bindgen-backend:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -30203,7 +28939,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-sys:0.48.0 +Package: wasm-bindgen-macro:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -30432,7 +29168,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-sys:0.52.0 +Package: wasm-bindgen-macro-support:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -30661,7 +29397,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.42.2 +Package: wasm-bindgen-shared:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -30890,7 +29626,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.48.5 +Package: winapi:0.3.9 The following copyrights and licenses were found in the source code of this package: @@ -31119,7 +29855,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.52.6 +Package: winapi-i686-pc-windows-gnu:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -31348,7 +30084,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.42.2 +Package: winapi-x86_64-pc-windows-gnu:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -31577,7 +30313,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.48.5 +Package: windows-core:0.52.0 The following copyrights and licenses were found in the source code of this package: @@ -31806,7 +30542,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.52.6 +Package: windows-sys:0.45.0 The following copyrights and licenses were found in the source code of this package: @@ -32035,7 +30771,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.42.2 +Package: windows-sys:0.52.0 The following copyrights and licenses were found in the source code of this package: @@ -32264,7 +31000,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.48.5 +Package: windows-targets:0.42.2 The following copyrights and licenses were found in the source code of this package: @@ -32493,7 +31229,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.52.6 +Package: windows-targets:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -32722,7 +31458,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.42.2 +Package: windows_aarch64_gnullvm:0.42.2 The following copyrights and licenses were found in the source code of this package: @@ -32951,7 +31687,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.48.5 +Package: windows_aarch64_gnullvm:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -33180,7 +31916,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.52.6 +Package: windows_aarch64_msvc:0.42.2 The following copyrights and licenses were found in the source code of this package: @@ -33409,7 +32145,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnullvm:0.52.6 +Package: windows_aarch64_msvc:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -33638,7 +32374,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.42.2 +Package: windows_i686_gnu:0.42.2 The following copyrights and licenses were found in the source code of this package: @@ -33867,7 +32603,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.48.5 +Package: windows_i686_gnu:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -34096,7 +32832,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.52.6 +Package: windows_i686_gnullvm:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -34325,7 +33061,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.42.2 +Package: windows_i686_msvc:0.42.2 The following copyrights and licenses were found in the source code of this package: @@ -34554,7 +33290,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.48.5 +Package: windows_i686_msvc:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -34783,7 +33519,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.52.6 +Package: windows_x86_64_gnu:0.42.2 The following copyrights and licenses were found in the source code of this package: @@ -35012,7 +33748,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnullvm:0.42.2 +Package: windows_x86_64_gnu:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -35241,7 +33977,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnullvm:0.48.5 +Package: windows_x86_64_gnullvm:0.42.2 The following copyrights and licenses were found in the source code of this package: @@ -35928,235 +34664,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_msvc:0.48.5 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - Package: windows_x86_64_msvc:0.52.6 The following copyrights and licenses were found in the source code of this package: diff --git a/java/benchmarks/src/main/java/glide/benchmarks/BenchmarkingApp.java b/java/benchmarks/src/main/java/glide/benchmarks/BenchmarkingApp.java index 31ab7bbd13..7f7f2de254 100644 --- a/java/benchmarks/src/main/java/glide/benchmarks/BenchmarkingApp.java +++ b/java/benchmarks/src/main/java/glide/benchmarks/BenchmarkingApp.java @@ -176,6 +176,10 @@ private static RunConfiguration verifyOptions(CommandLine line) throws ParseExce runConfiguration.host = line.getOptionValue("host"); } + if (line.hasOption("port")) { + runConfiguration.port = Integer.parseInt(line.getOptionValue("port")); + } + if (line.hasOption("clientCount")) { runConfiguration.clientCount = parseIntListOption(line.getOptionValue("clientCount")); } diff --git a/java/client/build.gradle b/java/client/build.gradle index a1543bb85b..0178f311ea 100644 --- a/java/client/build.gradle +++ b/java/client/build.gradle @@ -4,7 +4,9 @@ plugins { id 'java-library' id 'maven-publish' id 'signing' - id ("com.github.spotbugs") version "6.0.18" + id 'io.freefair.lombok' version '8.6' + id 'com.github.spotbugs' version '6.0.18' + id 'com.google.osdetector' version '1.7.3' } repositories { @@ -156,7 +158,11 @@ tasks.register('copyNativeLib', Copy) { into sourceSets.main.output.resourcesDir } +def defaultReleaseVersion = "255.255.255"; + +delombok.dependsOn('compileJava') jar.dependsOn('copyNativeLib') +javadoc.dependsOn('copyNativeLib') copyNativeLib.dependsOn('buildRustRelease') compileTestJava.dependsOn('copyNativeLib') test.dependsOn('buildRust') @@ -177,16 +183,13 @@ sourceSets { } } -// version is replaced during released workflow java-cd.yml -def defaultReleaseVersion = "255.255.255"; - publishing { publications { mavenJava(MavenPublication) { from components.java groupId = 'io.valkey' artifactId = 'valkey-glide' - version = System.getenv("GLIDE_LOCAL_VERSION") ?: defaultReleaseVersion; + version = System.getenv("GLIDE_RELEASE_VERSION") ?: defaultReleaseVersion; pom { name = 'valkey-glide' description = 'General Language Independent Driver for the Enterprise (GLIDE) for Valkey' @@ -218,8 +221,14 @@ publishing { } } +java { + modularity.inferModulePath = true + withSourcesJar() + withJavadocJar() +} + tasks.withType(Sign) { - def releaseVersion = System.getenv("GLIDE_LOCAL_VERSION") ?: defaultReleaseVersion; + def releaseVersion = System.getenv("GLIDE_RELEASE_VERSION") ?: defaultReleaseVersion; def isReleaseVersion = !releaseVersion.endsWith("SNAPSHOT") && releaseVersion != defaultReleaseVersion; onlyIf("isReleaseVersion is set") { isReleaseVersion } } @@ -239,9 +248,24 @@ tasks.withType(Test) { } jar { - archiveBaseName = "valkey-glide" - // placeholder will be renamed by platform+arch on the release workflow java-cd.yml - archiveClassifier = System.getenv("GLIDE_LOCAL_CLASSIFIER") ?: "placeholder" + archiveClassifier = osdetector.classifier +} + +sourcesJar { + // suppress following error + // Entry glide/api/BaseClient.java is a duplicate but no duplicate handling strategy has been set + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +delombok { + modulePath = classpath +} + +javadoc { + dependsOn delombok + source = delombok.outputs + options.tags = [ "example:a:Example:" ] + failOnError = false // TODO fix all javadoc errors and warnings and remove that } spotbugsMain { diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index fceaf82634..0638201f9e 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -83,6 +83,9 @@ import static command_request.CommandRequestOuterClass.RequestType.PfAdd; import static command_request.CommandRequestOuterClass.RequestType.PfCount; import static command_request.CommandRequestOuterClass.RequestType.PfMerge; +import static command_request.CommandRequestOuterClass.RequestType.PubSubChannels; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumPat; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumSub; import static command_request.CommandRequestOuterClass.RequestType.Publish; import static command_request.CommandRequestOuterClass.RequestType.RPop; import static command_request.CommandRequestOuterClass.RequestType.RPush; @@ -187,6 +190,8 @@ import static glide.utils.ArrayTransformUtils.convertMapToKeyValueStringArray; import static glide.utils.ArrayTransformUtils.convertMapToValueKeyStringArray; import static glide.utils.ArrayTransformUtils.convertMapToValueKeyStringArrayBinary; +import static glide.utils.ArrayTransformUtils.convertNestedArrayToKeyValueGlideStringArray; +import static glide.utils.ArrayTransformUtils.convertNestedArrayToKeyValueStringArray; import static glide.utils.ArrayTransformUtils.mapGeoDataToArray; import static glide.utils.ArrayTransformUtils.mapGeoDataToGlideStringArray; @@ -203,6 +208,7 @@ import glide.api.commands.StreamBaseCommands; import glide.api.commands.StringBaseCommands; import glide.api.commands.TransactionsBaseCommands; +import glide.api.models.ClusterValue; import glide.api.models.GlideString; import glide.api.models.PubSubMessage; import glide.api.models.Script; @@ -691,7 +697,7 @@ protected Map[] handleFunctionListResponseBinary(Object[] r return data; } - /** Process a FUNCTION STATS standalone response. */ + /** Process a FUNCTION STATS response from one node. */ protected Map> handleFunctionStatsResponse( Map> response) { Map runningScriptInfo = response.get("running_script"); @@ -702,7 +708,7 @@ protected Map> handleFunctionStatsResponse( return response; } - /** Process a FUNCTION STATS standalone response. */ + /** Process a FUNCTION STATS response from one node. */ protected Map> handleFunctionStatsBinaryResponse( Map> response) { Map runningScriptInfo = response.get(gs("running_script")); @@ -713,6 +719,36 @@ protected Map> handleFunctionStatsBinaryRe return response; } + /** Process a FUNCTION STATS cluster response. */ + protected ClusterValue>> handleFunctionStatsResponse( + Response response, boolean isSingleValue) { + if (isSingleValue) { + return ClusterValue.ofSingleValue(handleFunctionStatsResponse(handleMapResponse(response))); + } else { + Map>> data = handleMapResponse(response); + for (var nodeInfo : data.entrySet()) { + nodeInfo.setValue(handleFunctionStatsResponse(nodeInfo.getValue())); + } + return ClusterValue.ofMultiValue(data); + } + } + + /** Process a FUNCTION STATS cluster response. */ + protected ClusterValue>> + handleFunctionStatsBinaryResponse(Response response, boolean isSingleValue) { + if (isSingleValue) { + return ClusterValue.ofSingleValue( + handleFunctionStatsBinaryResponse(handleBinaryStringMapResponse(response))); + } else { + Map>> data = + handleBinaryStringMapResponse(response); + for (var nodeInfo : data.entrySet()) { + nodeInfo.setValue(handleFunctionStatsBinaryResponse(nodeInfo.getValue())); + } + return ClusterValue.ofMultiValueBinary(data); + } + } + /** Process a LCS key1 key2 IDX response */ protected Map handleLcsIdxResponse(Map response) throws GlideException { @@ -2588,12 +2624,23 @@ public CompletableFuture xadd(@NonNull String key, @NonNull Map xadd(@NonNull String key, @NonNull String[][] values) { + return xadd(key, values, StreamAddOptions.builder().build()); + } + @Override public CompletableFuture xadd( @NonNull GlideString key, @NonNull Map values) { return xadd(key, values, StreamAddOptionsBinary.builder().build()); } + @Override + public CompletableFuture xadd( + @NonNull GlideString key, @NonNull GlideString[][] values) { + return xadd(key, values, StreamAddOptionsBinary.builder().build()); + } + @Override public CompletableFuture xadd( @NonNull String key, @NonNull Map values, @NonNull StreamAddOptions options) { @@ -2603,6 +2650,16 @@ public CompletableFuture xadd( return commandManager.submitNewCommand(XAdd, arguments, this::handleStringOrNullResponse); } + @Override + public CompletableFuture xadd( + @NonNull String key, @NonNull String[][] values, @NonNull StreamAddOptions options) { + String[] arguments = + ArrayUtils.addAll( + ArrayUtils.addFirst(options.toArgs(), key), + convertNestedArrayToKeyValueStringArray(values)); + return commandManager.submitNewCommand(XAdd, arguments, this::handleStringOrNullResponse); + } + @Override public CompletableFuture xadd( @NonNull GlideString key, @@ -2618,6 +2675,21 @@ public CompletableFuture xadd( return commandManager.submitNewCommand(XAdd, arguments, this::handleGlideStringOrNullResponse); } + @Override + public CompletableFuture xadd( + @NonNull GlideString key, + @NonNull GlideString[][] values, + @NonNull StreamAddOptionsBinary options) { + GlideString[] arguments = + new ArgsBuilder() + .add(key) + .add(options.toArgs()) + .add(convertNestedArrayToKeyValueGlideStringArray(values)) + .toArray(); + + return commandManager.submitNewCommand(XAdd, arguments, this::handleGlideStringOrNullResponse); + } + @Override public CompletableFuture>> xread( @NonNull Map keysAndIds) { @@ -2706,7 +2778,9 @@ public CompletableFuture> xrange( @NonNull String key, @NonNull StreamRange start, @NonNull StreamRange end, long count) { String[] arguments = ArrayUtils.addFirst(StreamRange.toArgs(start, end, count), key); return commandManager.submitNewCommand( - XRange, arguments, response -> castMapOf2DArray(handleMapResponse(response), String.class)); + XRange, + arguments, + response -> castMapOf2DArray(handleMapOrNullResponse(response), String.class)); } @Override @@ -2719,7 +2793,8 @@ public CompletableFuture> xrange( return commandManager.submitNewCommand( XRange, arguments, - response -> castMapOf2DArray(handleBinaryStringMapResponse(response), GlideString.class)); + response -> + castMapOf2DArray(handleBinaryStringMapOrNullResponse(response), GlideString.class)); } @Override @@ -2752,7 +2827,7 @@ public CompletableFuture> xrevrange( return commandManager.submitNewCommand( XRevRange, arguments, - response -> castMapOf2DArray(handleMapResponse(response), String.class)); + response -> castMapOf2DArray(handleMapOrNullResponse(response), String.class)); } @Override @@ -2765,7 +2840,8 @@ public CompletableFuture> xrevrange( return commandManager.submitNewCommand( XRevRange, arguments, - response -> castMapOf2DArray(handleBinaryStringMapResponse(response), GlideString.class)); + response -> + castMapOf2DArray(handleBinaryStringMapOrNullResponse(response), GlideString.class)); } @Override @@ -4453,6 +4529,54 @@ public CompletableFuture publish( }); } + @Override + public CompletableFuture pubsubChannels() { + return commandManager.submitNewCommand( + PubSubChannels, + new String[0], + response -> castArray(handleArrayResponse(response), String.class)); + } + + @Override + public CompletableFuture pubsubChannelsBinary() { + return commandManager.submitNewCommand( + PubSubChannels, + new GlideString[0], + response -> castArray(handleArrayResponseBinary(response), GlideString.class)); + } + + @Override + public CompletableFuture pubsubChannels(@NonNull String pattern) { + return commandManager.submitNewCommand( + PubSubChannels, + new String[] {pattern}, + response -> castArray(handleArrayResponse(response), String.class)); + } + + @Override + public CompletableFuture pubsubChannels(@NonNull GlideString pattern) { + return commandManager.submitNewCommand( + PubSubChannels, + new GlideString[] {pattern}, + response -> castArray(handleArrayResponseBinary(response), GlideString.class)); + } + + @Override + public CompletableFuture pubsubNumPat() { + return commandManager.submitNewCommand(PubSubNumPat, new String[0], this::handleLongResponse); + } + + @Override + public CompletableFuture> pubsubNumSub(@NonNull String[] channels) { + return commandManager.submitNewCommand(PubSubNumSub, channels, this::handleMapResponse); + } + + @Override + public CompletableFuture> pubsubNumSub(@NonNull GlideString[] channels) { + return commandManager.submitNewCommand( + PubSubNumSub, channels, this::handleBinaryStringMapResponse); + } + @Override public CompletableFuture watch(@NonNull String[] keys) { return commandManager.submitNewCommand(Watch, keys, this::handleStringResponse); diff --git a/java/client/src/main/java/glide/api/GlideClient.java b/java/client/src/main/java/glide/api/GlideClient.java index 53eaeb369d..32c5a5cc28 100644 --- a/java/client/src/main/java/glide/api/GlideClient.java +++ b/java/client/src/main/java/glide/api/GlideClient.java @@ -96,6 +96,12 @@ public CompletableFuture customCommand(@NonNull String[] args) { return commandManager.submitNewCommand(CustomCommand, args, this::handleObjectOrNullResponse); } + @Override + public CompletableFuture customCommand(@NonNull GlideString[] args) { + return commandManager.submitNewCommand( + CustomCommand, args, this::handleBinaryObjectOrNullResponse); + } + @Override public CompletableFuture exec(@NonNull Transaction transaction) { if (transaction.isBinaryOutput()) { @@ -438,19 +444,20 @@ public CompletableFuture functionKill() { } @Override - public CompletableFuture>> functionStats() { + public CompletableFuture>>> functionStats() { return commandManager.submitNewCommand( FunctionStats, new String[0], - response -> handleFunctionStatsResponse(handleMapResponse(response))); + response -> handleFunctionStatsResponse(response, false).getMultiValue()); } @Override - public CompletableFuture>> functionStatsBinary() { + public CompletableFuture>>> + functionStatsBinary() { return commandManager.submitNewCommand( FunctionStats, new GlideString[0], - response -> handleFunctionStatsBinaryResponse(handleBinaryStringMapResponse(response))); + response -> handleFunctionStatsBinaryResponse(response, false).getMultiValue()); } @Override diff --git a/java/client/src/main/java/glide/api/GlideClusterClient.java b/java/client/src/main/java/glide/api/GlideClusterClient.java index 65b8721ec8..2eb52c10ef 100644 --- a/java/client/src/main/java/glide/api/GlideClusterClient.java +++ b/java/client/src/main/java/glide/api/GlideClusterClient.java @@ -106,6 +106,15 @@ public CompletableFuture> customCommand(@NonNull String[] a CustomCommand, args, response -> ClusterValue.of(handleObjectOrNullResponse(response))); } + @Override + public CompletableFuture> customCommand(@NonNull GlideString[] args) { + // TODO if a command returns a map as a single value, ClusterValue misleads user + return commandManager.submitNewCommand( + CustomCommand, + args, + response -> ClusterValue.of(handleBinaryObjectOrNullResponse(response))); + } + @Override public CompletableFuture> customCommand( @NonNull String[] args, @NonNull Route route) { @@ -113,6 +122,13 @@ public CompletableFuture> customCommand( CustomCommand, args, route, response -> handleCustomCommandResponse(route, response)); } + @Override + public CompletableFuture> customCommand( + @NonNull GlideString[] args, @NonNull Route route) { + return commandManager.submitNewCommand( + CustomCommand, args, route, response -> handleCustomCommandBinaryResponse(route, response)); + } + protected ClusterValue handleCustomCommandResponse(Route route, Response response) { if (route instanceof SingleNodeRoute) { return ClusterValue.ofSingleValue(handleObjectOrNullResponse(response)); @@ -123,6 +139,16 @@ protected ClusterValue handleCustomCommandResponse(Route route, Response return ClusterValue.ofMultiValue(handleMapResponse(response)); } + protected ClusterValue handleCustomCommandBinaryResponse(Route route, Response response) { + if (route instanceof SingleNodeRoute) { + return ClusterValue.ofSingleValue(handleBinaryObjectOrNullResponse(response)); + } + if (response.hasConstantResponse()) { + return ClusterValue.ofSingleValue(handleStringResponse(response)); + } + return ClusterValue.ofMultiValueBinary(handleBinaryStringMapResponse(response)); + } + @Override public CompletableFuture exec(@NonNull ClusterTransaction transaction) { if (transaction.isBinaryOutput()) { @@ -920,36 +946,6 @@ public CompletableFuture functionKill(@NonNull Route route) { FunctionKill, new String[0], route, this::handleStringResponse); } - /** Process a FUNCTION STATS cluster response. */ - protected ClusterValue>> handleFunctionStatsResponse( - Response response, boolean isSingleValue) { - if (isSingleValue) { - return ClusterValue.ofSingleValue(handleFunctionStatsResponse(handleMapResponse(response))); - } else { - Map>> data = handleMapResponse(response); - for (var nodeInfo : data.entrySet()) { - nodeInfo.setValue(handleFunctionStatsResponse(nodeInfo.getValue())); - } - return ClusterValue.ofMultiValue(data); - } - } - - /** Process a FUNCTION STATS cluster response. */ - protected ClusterValue>> - handleFunctionStatsBinaryResponse(Response response, boolean isSingleValue) { - if (isSingleValue) { - return ClusterValue.ofSingleValue( - handleFunctionStatsBinaryResponse(handleBinaryStringMapResponse(response))); - } else { - Map>> data = - handleBinaryStringMapResponse(response); - for (var nodeInfo : data.entrySet()) { - nodeInfo.setValue(handleFunctionStatsBinaryResponse(nodeInfo.getValue())); - } - return ClusterValue.ofMultiValueBinary(data); - } - } - @Override public CompletableFuture>>> functionStats() { return commandManager.submitNewCommand( diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index a2d4c92a7d..9eb5bc9a45 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -1153,8 +1153,8 @@ CompletableFuture pexpireAt( * user. * * @see valkey.io for details. - * @param key The key of the set. - * @return The serialized value of a set.
+ * @param key The key to serialize. + * @return The serialized value of the data stored at key.
* If key does not exist, null will be returned. * @example *
{@code
@@ -1171,10 +1171,10 @@ CompletableFuture pexpireAt(
      * deserializing the provided serialized value (obtained via {@link #dump}).
      *
      * @see valkey.io for details.
-     * @param key The key of the set.
+     * @param key The key to create.
      * @param ttl The expiry time (in milliseconds). If 0, the key will
      *     persist.
-     * @param value The serialized value.
+     * @param value The serialized value to deserialize and assign to key.
      * @return Return OK if successfully create a key with a value
      *      .
      * @example
@@ -1189,11 +1189,12 @@ CompletableFuture pexpireAt(
      * Create a key associated with a value that is obtained by
      * deserializing the provided serialized value (obtained via {@link #dump}).
      *
+     * @apiNote IDLETIME and FREQ modifiers cannot be set at the same time.
      * @see valkey.io for details.
-     * @param key The key of the set.
+     * @param key The key to create.
      * @param ttl The expiry time (in milliseconds). If 0, the key will
      *     persist.
-     * @param value The serialized value.
+     * @param value The serialized value to deserialize and assign to key.
      * @param restoreOptions The restore options. See {@link RestoreOptions}.
      * @return Return OK if successfully create a key with a value
      *      .
diff --git a/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java b/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java
index 544c40a2c1..dcd0fdd594 100644
--- a/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java
+++ b/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java
@@ -22,30 +22,45 @@ public interface GenericClusterCommands {
 
     /**
      * Executes a single command, without checking inputs. Every part of the command, including
-     * subcommands, should be added as a separate value in args.
-     *
-     * 

The command will be routed to all primaries. + * subcommands, should be added as a separate value in args.
+ * The command will be routed automatically based on the passed command's default request policy. * - * @apiNote See Valkey * GLIDE Wiki for details on the restrictions and limitations of the custom command API. * @param args Arguments for the custom command including the command name. - * @return The returning value depends on the executed command. + * @return The returned value for the custom command. * @example *

{@code
      * ClusterValue data = client.customCommand(new String[] {"ping"}).get();
-     * assert ((String) data.getSingleValue()).equals("PONG");
+     * assert data.getSingleValue().equals("PONG");
      * }
      */
     CompletableFuture> customCommand(String[] args);
 
     /**
      * Executes a single command, without checking inputs. Every part of the command, including
-     * subcommands, should be added as a separate value in args.
+     * subcommands, should be added as a separate value in args.
+ * The command will be routed automatically based on the passed command's default request policy. * - *

Client will route the command to the nodes defined by route. + * @see Valkey + * GLIDE Wiki for details on the restrictions and limitations of the custom command API. + * @param args Arguments for the custom command including the command name. + * @return The returned value for the custom command. + * @example + *

{@code
+     * ClusterValue data = client.customCommand(new GlideString[] {gs("ping")}).get();
+     * assert data.getSingleValue().equals(gs("PONG"));
+     * }
+     */
+    CompletableFuture> customCommand(GlideString[] args);
+
+    /**
+     * Executes a single command, without checking inputs. Every part of the command, including
+     * subcommands, should be added as a separate value in args.
      *
-     * @apiNote See Valkey
      *     GLIDE Wiki for details on the restrictions and limitations of the custom command API.
      * @param args Arguments for the custom command including the command name
@@ -56,12 +71,33 @@ public interface GenericClusterCommands {
      *     
{@code
      * ClusterValue result = clusterClient.customCommand(new String[]{ "CONFIG", "GET", "maxmemory"}, ALL_NODES).get();
      * Map payload = result.getMultiValue();
-     * assert ((String) payload.get("node1")).equals("1GB");
-     * assert ((String) payload.get("node2")).equals("100MB");
+     * assert payload.get("node1").equals("1GB");
+     * assert payload.get("node2").equals("100MB");
      * }
      */
     CompletableFuture> customCommand(String[] args, Route route);
 
+    /**
+     * Executes a single command, without checking inputs. Every part of the command, including
+     * subcommands, should be added as a separate value in args.
+     *
+     * @see Valkey
+     *     GLIDE Wiki for details on the restrictions and limitations of the custom command API.
+     * @param args Arguments for the custom command including the command name
+     * @param route Specifies the routing configuration for the command. The client will route the
+     *     command to the nodes defined by route.
+     * @return The returning value depends on the executed command and route.
+     * @example
+     *     
{@code
+     * ClusterValue result = clusterClient.customCommand(new GlideString[] { gs("CONFIG"), gs("GET"), gs("maxmemory") }, ALL_NODES).get();
+     * Map payload = result.getMultiValue();
+     * assert payload.get(gs("node1")).equals(gs("1GB"));
+     * assert payload.get(gs("node2")).equals(gs("100MB"));
+     * }
+     */
+    CompletableFuture> customCommand(GlideString[] args, Route route);
+
     /**
      * Executes a transaction by processing the queued commands.
      *
diff --git a/java/client/src/main/java/glide/api/commands/GenericCommands.java b/java/client/src/main/java/glide/api/commands/GenericCommands.java
index a40503a37e..ae72938bad 100644
--- a/java/client/src/main/java/glide/api/commands/GenericCommands.java
+++ b/java/client/src/main/java/glide/api/commands/GenericCommands.java
@@ -22,21 +22,40 @@ public interface GenericCommands {
      * Executes a single command, without checking inputs. Every part of the command, including
      * subcommands, should be added as a separate value in args.
      *
-     * @apiNote See Valkey
      *     GLIDE Wiki for details on the restrictions and limitations of the custom command API.
      * @param args Arguments for the custom command.
-     * @return The returning value depends on the executed command.
+     * @return The returned value for the custom command.
      * @example
      *     
{@code
-     * Object response = (String) client.customCommand(new String[] {"ping", "GLIDE"}).get();
-     * assert ((String) response).equals("GLIDE");
+     * Object response = client.customCommand(new String[] {"ping", "GLIDE"}).get();
+     * assert response.equals("GLIDE");
      * // Get a list of all pub/sub clients:
      * Object result = client.customCommand(new String[]{ "CLIENT", "LIST", "TYPE", "PUBSUB" }).get();
      * }
*/ CompletableFuture customCommand(String[] args); + /** + * Executes a single command, without checking inputs. Every part of the command, including + * subcommands, should be added as a separate value in args. + * + * @see Valkey + * GLIDE Wiki for details on the restrictions and limitations of the custom command API. + * @param args Arguments for the custom command. + * @return The returned value for the custom command. + * @example + *
{@code
+     * Object response = client.customCommand(new GlideString[] {gs("ping"), gs("GLIDE")}).get();
+     * assert response.equals(gs("GLIDE"));
+     * // Get a list of all pub/sub clients:
+     * Object result = client.customCommand(new GlideString[] { gs("CLIENT"), gs("LIST"), gs("TYPE"), gs("PUBSUB") }).get();
+     * }
+ */ + CompletableFuture customCommand(GlideString[] args); + /** * Executes a transaction by processing the queued commands. * diff --git a/java/client/src/main/java/glide/api/commands/GeospatialIndicesBaseCommands.java b/java/client/src/main/java/glide/api/commands/GeospatialIndicesBaseCommands.java index 7abb252747..86ae648faa 100644 --- a/java/client/src/main/java/glide/api/commands/GeospatialIndicesBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GeospatialIndicesBaseCommands.java @@ -768,7 +768,7 @@ CompletableFuture geosearch( * axis-aligned rectangle, determined by height and width. * * - * @return The number of elements in the resulting set. + * @return The number of elements in the resulting sorted set stored at destination. * @example *
{@code
      * Long result = client
@@ -812,7 +812,7 @@ CompletableFuture geosearchstore(
      *           axis-aligned rectangle, determined by height and width.
      *     
      *
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
@@ -861,7 +861,7 @@ CompletableFuture geosearchstore(
      *
      * @param resultOptions Optional inputs for sorting/limiting the results. See - {@link
      *     GeoSearchResultOptions}
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
@@ -912,7 +912,7 @@ CompletableFuture geosearchstore(
      *
      * @param resultOptions Optional inputs for sorting/limiting the results. See - {@link
      *     GeoSearchResultOptions}
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
@@ -962,7 +962,7 @@ CompletableFuture geosearchstore(
      *     
      *
      * @param options The optional inputs to request additional information.
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
@@ -1012,7 +1012,7 @@ CompletableFuture geosearchstore(
      *     
      *
      * @param options The optional inputs to request additional information.
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
@@ -1064,7 +1064,7 @@ CompletableFuture geosearchstore(
      * @param options The optional inputs to request additional information.
      * @param resultOptions Optional inputs for sorting/limiting the results. See - {@link
      *     GeoSearchResultOptions}
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
@@ -1118,7 +1118,7 @@ CompletableFuture geosearchstore(
      * @param options The optional inputs to request additional information.
      * @param resultOptions Optional inputs for sorting/limiting the results. See - {@link
      *     GeoSearchResultOptions}
-     * @return The number of elements in the resulting set.
+     * @return The number of elements in the resulting sorted set stored at destination.
      * @example
      *     
{@code
      * Long result = client
diff --git a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java
index 5bea78fed9..9140932fc0 100644
--- a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java
+++ b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java
@@ -591,7 +591,7 @@ public interface HashBaseCommands {
      */
     CompletableFuture hrandfieldWithCountWithValues(String key, long count);
 
-    /*
+    /**
      * Retrieves up to count random field names along with their values from the hash
      * value stored at key.
      *
diff --git a/java/client/src/main/java/glide/api/commands/PubSubBaseCommands.java b/java/client/src/main/java/glide/api/commands/PubSubBaseCommands.java
index 6906d98e06..d83038b3b3 100644
--- a/java/client/src/main/java/glide/api/commands/PubSubBaseCommands.java
+++ b/java/client/src/main/java/glide/api/commands/PubSubBaseCommands.java
@@ -2,6 +2,7 @@
 package glide.api.commands;
 
 import glide.api.models.GlideString;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -40,4 +41,124 @@ public interface PubSubBaseCommands {
      * }
*/ CompletableFuture publish(GlideString message, GlideString channel); + + /** + * Lists the currently active channels. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + * @see valkey.io for details. + * @return An Array of all active channels. + * @example + *
{@code
+     * String[] response = client.pubsubChannels().get();
+     * assert Arrays.equals(new String[] { "channel1", "channel2" });
+     * }
+ */ + CompletableFuture pubsubChannels(); + + /** + * Lists the currently active channels.
+ * Unlike of {@link #pubsubChannels()}, returns channel names as {@link GlideString}s. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + * @see valkey.io for details. + * @return An Array of all active channels. + * @example + *
{@code
+     * GlideString[] response = client.pubsubChannels().get();
+     * assert Arrays.equals(new GlideString[] { "channel1", "channel2" });
+     * }
+ */ + CompletableFuture pubsubChannelsBinary(); + + /** + * Lists the currently active channels. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + * @see valkey.io for details. + * @param pattern A glob-style pattern to match active channels. + * @return An Array of currently active channels matching the given pattern. + * @example + *
{@code
+     * String[] response = client.pubsubChannels("news.*").get();
+     * assert Arrays.equals(new String[] { "news.sports", "news.weather" });
+     * }
+ */ + CompletableFuture pubsubChannels(String pattern); + + /** + * Lists the currently active channels. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + * @see valkey.io for details. + * @param pattern A glob-style pattern to match active channels. + * @return An Array of currently active channels matching the given pattern. + * @example + *
{@code
+     * GlideString[] response = client.pubsubChannels(gs("news.*")).get();
+     * assert Arrays.equals(new GlideString[] { gs("news.sports"), gs("news.weather") });
+     * }
+ */ + CompletableFuture pubsubChannels(GlideString pattern); + + /** + * Returns the number of unique patterns that are subscribed to by clients. + * + * @apiNote + *
    + *
  • When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + *
  • This is the total number of unique patterns all the clients are subscribed to, not + * the count of clients subscribed to patterns. + *
+ * + * @see valkey.io for details. + * @return The number of unique patterns. + * @example + *
{@code
+     * Long result = client.pubsubNumPat().get();
+     * assert result == 3L;
+     * }
+ */ + CompletableFuture pubsubNumPat(); + + /** + * Returns the number of subscribers (exclusive of clients subscribed to patterns) for the + * specified channels. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single map. + * @see valkey.io for details. + * @param channels The list of channels to query for the number of subscribers. + * @return A Map where keys are the channel names and values are the numbers of + * subscribers. + * @example + *
{@code
+     * Map result = client.pubsubNumSub(new String[] {"channel1", "channel2"}).get();
+     * assert result.equals(Map.of("channel1", 3L, "channel2", 5L));
+     * }
+ */ + CompletableFuture> pubsubNumSub(String[] channels); + + /** + * Returns the number of subscribers (exclusive of clients subscribed to patterns) for the + * specified channels. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single map. + * @see valkey.io for details. + * @param channels The list of channels to query for the number of subscribers. + * @return A Map where keys are the channel names and values are the numbers of + * subscribers. + * @example + *
{@code
+     * Map result = client.pubsubNumSub(new GlideString[] {gs("channel1"), gs("channel2")}).get();
+     * assert result.equals(Map.of(gs("channel1"), 3L, gs("channel2"), 5L));
+     * }
+ */ + CompletableFuture> pubsubNumSub(GlideString[] channels); } diff --git a/java/client/src/main/java/glide/api/commands/ScriptingAndFunctionsClusterCommands.java b/java/client/src/main/java/glide/api/commands/ScriptingAndFunctionsClusterCommands.java index 885fc16b81..0c4a9a891f 100644 --- a/java/client/src/main/java/glide/api/commands/ScriptingAndFunctionsClusterCommands.java +++ b/java/client/src/main/java/glide/api/commands/ScriptingAndFunctionsClusterCommands.java @@ -865,7 +865,7 @@ CompletableFuture> fcallReadOnly( /** * Kills a function that is currently executing.
* FUNCTION KILL terminates read-only functions only.
- * The command will be routed to all primary nodes. + * The command will be routed to all nodes. * * @since Valkey 7.0 and above. * @see valkey.io for details. diff --git a/java/client/src/main/java/glide/api/commands/ScriptingAndFunctionsCommands.java b/java/client/src/main/java/glide/api/commands/ScriptingAndFunctionsCommands.java index f6ca4890bb..7cb5bb3f36 100644 --- a/java/client/src/main/java/glide/api/commands/ScriptingAndFunctionsCommands.java +++ b/java/client/src/main/java/glide/api/commands/ScriptingAndFunctionsCommands.java @@ -328,7 +328,8 @@ CompletableFuture[]> functionListBinary( /** * Kills a function that is currently executing.
- * FUNCTION KILL terminates read-only functions only. + * FUNCTION KILL terminates read-only functions only. FUNCTION KILL runs + * on all nodes of the server, including primary and replicas. * * @since Valkey 7.0 and above. * @see valkey.io for details. @@ -343,11 +344,14 @@ CompletableFuture[]> functionListBinary( /** * Returns information about the function that's currently running and information about the - * available execution engines. + * available execution engines.
+ * FUNCTION STATS runs on all nodes of the server, including primary and replicas. + * The response includes a mapping from node address to the command response for that node. * * @since Valkey 7.0 and above. * @see valkey.io for details. - * @return A Map with two keys: + * @return A Map from node address to the command response for that node, where the + * command contains a Map with two keys: *
    *
  • running_script with information about the running script. *
  • engines with information about available engines and their stats. @@ -355,30 +359,35 @@ CompletableFuture[]> functionListBinary( * See example for more details. * @example *
    {@code
    -     * Map> response = client.functionStats().get();
    -     * Map runningScriptInfo = response.get("running_script");
    -     * if (runningScriptInfo != null) {
    -     *   String[] commandLine = (String[]) runningScriptInfo.get("command");
    -     *   System.out.printf("Server is currently running function '%s' with command line '%s', which has been running for %d ms%n",
    -     *       runningScriptInfo.get("name"), String.join(" ", commandLine), (long)runningScriptInfo.get("duration_ms"));
    -     * }
    -     * Map enginesInfo = response.get("engines");
    -     * for (String engineName : enginesInfo.keySet()) {
    -     *   Map engine = (Map) enginesInfo.get(engineName);
    -     *   System.out.printf("Server supports engine '%s', which has %d libraries and %d functions in total%n",
    -     *       engineName, engine.get("libraries_count"), engine.get("functions_count"));
    +     * Map>> response = client.functionStats().get();
    +     * for (String node : response.keySet()) {
    +     *   Map runningScriptInfo = response.get(node).get("running_script");
    +     *   if (runningScriptInfo != null) {
    +     *     String[] commandLine = (String[]) runningScriptInfo.get("command");
    +     *     System.out.printf("Node '%s' is currently running function '%s' with command line '%s', which has been running for %d ms%n",
    +     *         node, runningScriptInfo.get("name"), String.join(" ", commandLine), (long)runningScriptInfo.get("duration_ms"));
    +     *   }
    +     *   Map enginesInfo = response.get(node).get("engines");
    +     *   for (String engineName : enginesInfo.keySet()) {
    +     *     Map engine = (Map) enginesInfo.get(engineName);
    +     *     System.out.printf("Node '%s' supports engine '%s', which has %d libraries and %d functions in total%n",
    +     *         node, engineName, engine.get("libraries_count"), engine.get("functions_count"));
    +     *   }
          * }
          * }
    */ - CompletableFuture>> functionStats(); + CompletableFuture>>> functionStats(); /** * Returns information about the function that's currently running and information about the - * available execution engines. + * available execution engines.
    + * FUNCTION STATS runs on all nodes of the server, including primary and replicas. + * The response includes a mapping from node address to the command response for that node. * * @since Valkey 7.0 and above. * @see valkey.io for details. - * @return A Map with two keys: + * @return A Map from node address to the command response for that node, where the + * command contains a Map with two keys: *
      *
    • running_script with information about the running script. *
    • engines with information about available engines and their stats. @@ -386,20 +395,22 @@ CompletableFuture[]> functionListBinary( * See example for more details. * @example *
      {@code
      -     * Map> response = client.functionStats().get();
      -     * Map runningScriptInfo = response.get(gs("running_script"));
      -     * if (runningScriptInfo != null) {
      -     *   GlideString[] commandLine = (GlideString[]) runningScriptInfo.get(gs("command"));
      -     *   System.out.printf("Server is currently running function '%s' with command line '%s', which has been running for %d ms%n",
      -     *       runningScriptInfo.get(gs("name")), String.join(" ", Arrays.toString(commandLine)), (long)runningScriptInfo.get(gs("duration_ms")));
      -     * }
      -     * Map enginesInfo = response.get(gs("engines"));
      -     * for (GlideString engineName : enginesInfo.keySet()) {
      -     *   Map engine = (Map) enginesInfo.get(gs(engineName));
      -     *   System.out.printf("Server supports engine '%s', which has %d libraries and %d functions in total%n",
      -     *       engineName, engine.get(gs("libraries_count")), engine.get(gs("functions_count")));
      +     * Map>> response = client.functionStats().get();
      +     * for (String node : response.keySet()) {
      +     *   Map runningScriptInfo = response.get(gs(node)).get(gs("running_script"));
      +     *   if (runningScriptInfo != null) {
      +     *     GlideString[] commandLine = (GlideString[]) runningScriptInfo.get(gs("command"));
      +     *     System.out.printf("Node '%s' is currently running function '%s' with command line '%s', which has been running for %d ms%n",
      +     *         node, runningScriptInfo.get(gs("name")), String.join(" ", Arrays.toString(commandLine)), (long)runningScriptInfo.get(gs("duration_ms")));
      +     *   }
      +     *   Map enginesInfo = response.get(gs(node)).get(gs("engines"));
      +     *   for (GlideString engineName : enginesInfo.keySet()) {
      +     *     Map engine = (Map) enginesInfo.get(gs(engineName));
      +     *     System.out.printf("Node '%s' supports engine '%s', which has %d libraries and %d functions in total%n",
      +     *         node, engineName, engine.get(gs("libraries_count")), engine.get(gs("functions_count")));
      +     *   }
            * }
            * }
      */ - CompletableFuture>> functionStatsBinary(); + CompletableFuture>>> functionStatsBinary(); } diff --git a/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java index c88bda1201..b2b663aaa3 100644 --- a/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java @@ -29,7 +29,8 @@ public interface StreamBaseCommands { /** * Adds an entry to the specified stream stored at key.
      - * If the key doesn't exist, the stream is created. + * If the key doesn't exist, the stream is created. To add entries with duplicate + * keys, use {@link #xadd(String, String[][])}. * * @see valkey.io for details. * @param key The key of the stream. @@ -45,7 +46,25 @@ public interface StreamBaseCommands { /** * Adds an entry to the specified stream stored at key.
      - * If the key doesn't exist, the stream is created. + * If the key doesn't exist, the stream is created. This method overload allows + * entries with duplicate keys to be added. + * + * @see valkey.io for details. + * @param key The key of the stream. + * @param values Field-value pairs to be added to the entry. + * @return The id of the added entry. + * @example + *
      {@code
      +     * String streamId = client.xadd("key", new String[][] {{"name", "Sara"}, {"surname", "OConnor"}}).get();
      +     * System.out.println("Stream: " + streamId);
      +     * }
      + */ + CompletableFuture xadd(String key, String[][] values); + + /** + * Adds an entry to the specified stream stored at key.
      + * If the key doesn't exist, the stream is created. To add entries with duplicate + * keys, use {@link #xadd(GlideString, GlideString[][])}. * * @see valkey.io for details. * @param key The key of the stream. @@ -61,7 +80,25 @@ public interface StreamBaseCommands { /** * Adds an entry to the specified stream stored at key.
      - * If the key doesn't exist, the stream is created. + * If the key doesn't exist, the stream is created. This method overload allows + * entries with duplicate keys to be added. + * + * @see valkey.io for details. + * @param key The key of the stream. + * @param values Field-value pairs to be added to the entry. + * @return The id of the added entry. + * @example + *
      {@code
      +     * String streamId = client.xadd(gs("key"), new String[][] {{gs("name"), gs("Sara")}, {gs("surname"), gs("OConnor")}}).get();
      +     * System.out.println("Stream: " + streamId);
      +     * }
      + */ + CompletableFuture xadd(GlideString key, GlideString[][] values); + + /** + * Adds an entry to the specified stream stored at key.
      + * If the key doesn't exist, the stream is created. To add entries with duplicate + * keys, use {@link #xadd(String, String[][], StreamAddOptions)}. * * @see valkey.io for details. * @param key The key of the stream. @@ -73,10 +110,10 @@ public interface StreamBaseCommands { * @example *
      {@code
            * // Option to use the existing stream, or return null if the stream doesn't already exist at "key"
      -     * StreamAddOptions options = StreamAddOptions.builder().id("sid").makeStream(Boolean.FALSE).build();
      +     * StreamAddOptions options = StreamAddOptions.builder().id("1-0").makeStream(Boolean.FALSE).build();
            * String streamId = client.xadd("key", Map.of("name", "Sara", "surname", "OConnor"), options).get();
            * if (streamId != null) {
      -     *     assert streamId.equals("sid");
      +     *     assert streamId.equals("1-0");
            * }
            * }
      */ @@ -84,7 +121,32 @@ public interface StreamBaseCommands { /** * Adds an entry to the specified stream stored at key.
      - * If the key doesn't exist, the stream is created. + * If the key doesn't exist, the stream is created. This method overload allows + * entries with duplicate keys to be added. + * + * @see valkey.io for details. + * @param key The key of the stream. + * @param values Field-value pairs to be added to the entry. + * @param options Stream add options {@link StreamAddOptions}. + * @return The id of the added entry, or null if {@link + * StreamAddOptionsBuilder#makeStream(Boolean)} is set to false and no stream + * with the matching key exists. + * @example + *
      {@code
      +     * // Option to use the existing stream, or return null if the stream doesn't already exist at "key"
      +     * StreamAddOptions options = StreamAddOptions.builder().id("1-0").makeStream(Boolean.FALSE).build();
      +     * String streamId = client.xadd("key", new String[][] {{"name", "Sara"}, {"surname", "OConnor"}}, options).get();
      +     * if (streamId != null) {
      +     *     assert streamId.equals("1-0");
      +     * }
      +     * }
      + */ + CompletableFuture xadd(String key, String[][] values, StreamAddOptions options); + + /** + * Adds an entry to the specified stream stored at key.
      + * If the key doesn't exist, the stream is created. To add entries with duplicate + * keys, use {@link #xadd(GlideString, GlideString[][], StreamAddOptionsBinary)}. * * @see valkey.io for details. * @param key The key of the stream. @@ -96,27 +158,51 @@ public interface StreamBaseCommands { * @example *
      {@code
            * // Option to use the existing stream, or return null if the stream doesn't already exist at "key"
      -     * StreamAddOptionsBinary options = StreamAddOptions.builder().id(gs("sid")).makeStream(Boolean.FALSE).build();
      +     * StreamAddOptionsBinary options = StreamAddOptions.builder().id(gs("1-0")).makeStream(Boolean.FALSE).build();
            * String streamId = client.xadd(gs("key"), Map.of(gs("name"), gs("Sara"), gs("surname"), gs("OConnor")), options).get();
            * if (streamId != null) {
      -     *     assert streamId.equals("sid");
      +     *     assert streamId.equals("1-0");
            * }
            * }
      */ CompletableFuture xadd( GlideString key, Map values, StreamAddOptionsBinary options); + /** + * Adds an entry to the specified stream stored at key.
      + * If the key doesn't exist, the stream is created. This method overload allows + * entries with duplicate keys to be added. + * + * @see valkey.io for details. + * @param key The key of the stream. + * @param values Field-value pairs to be added to the entry. + * @param options Stream add options {@link StreamAddOptions}. + * @return The id of the added entry, or null if {@link + * StreamAddOptionsBinaryBuilder#makeStream(Boolean)} is set to false and no + * stream with the matching key exists. + * @example + *
      {@code
      +     * // Option to use the existing stream, or return null if the stream doesn't already exist at "key"
      +     * StreamAddOptionsBinary options = StreamAddOptions.builder().id(gs("1-0")).makeStream(Boolean.FALSE).build();
      +     * String streamId = client.xadd(gs("key"), new GlideString[][] {{gs("name"), gs("Sara")}, {gs("surname"), gs("OConnor")}}, options).get();
      +     * if (streamId != null) {
      +     *     assert streamId.equals("1-0");
      +     * }
      +     * }
      + */ + CompletableFuture xadd( + GlideString key, GlideString[][] values, StreamAddOptionsBinary options); + /** * Reads entries from the given streams. * * @apiNote When in cluster mode, all keys in keysAndIds must map to the same hash * slot. * @see valkey.io for details. - * @param keysAndIds A Map of keys and entry ids to read from. The - * Map is composed of a stream's key and the id of the entry after which the stream - * will be read. - * @return A {@literal Map>} with stream - * keys, to Map of stream-ids, to an array of pairings with format [[field, entry], [field, entry], ...]. + * @param keysAndIds A Map of keys and entry IDs to read from. + * @return A {@literal Map>} with stream keys, to + * Map of stream entry IDs, to an array of pairings with format + * [[field, entry], [field, entry], ...]. * @example *
      {@code
            * Map xreadKeys = Map.of("streamKey", "0-0");
      @@ -125,9 +211,10 @@ CompletableFuture xadd(
            *     System.out.printf("Key: %s", keyEntry.getKey());
            *     for (var streamEntry : keyEntry.getValue().entrySet()) {
            *         Arrays.stream(streamEntry.getValue()).forEach(entity ->
      -     *             System.out.printf("stream id: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
      +     *             System.out.printf("stream entry ID: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
            *         );
            *     }
      +     * }
            * }
      */ CompletableFuture>> xread(Map keysAndIds); @@ -138,11 +225,10 @@ CompletableFuture xadd( * @apiNote When in cluster mode, all keys in keysAndIds must map to the same hash * slot. * @see valkey.io for details. - * @param keysAndIds A Map of keys and entry ids to read from. The - * Map is composed of a stream's key and the id of the entry after which the stream - * will be read. - * @return A {@literal Map>} with stream - * keys, to Map of stream-ids, to an array of pairings with format [[field, entry], [field, entry], ...]. + * @param keysAndIds A Map of keys and entry IDs to read from. + * @return A {@literal Map>} with stream keys, to + * Map of stream entry IDs, to an array of pairings with format + * [[field, entry], [field, entry], ...]. * @example *
      {@code
            * Map xreadKeys = Map.of(gs("streamKey"), gs("0-0"));
      @@ -151,9 +237,10 @@ CompletableFuture xadd(
            *     System.out.printf("Key: %s", keyEntry.getKey());
            *     for (var streamEntry : keyEntry.getValue().entrySet()) {
            *         Arrays.stream(streamEntry.getValue()).forEach(entity ->
      -     *             System.out.printf("stream id: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
      +     *             System.out.printf("stream entry ID: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
            *         );
            *     }
      +     * }
            * }
      */ CompletableFuture>> xreadBinary( @@ -165,12 +252,11 @@ CompletableFuture>> xreadBina * @apiNote When in cluster mode, all keys in keysAndIds must map to the same hash * slot. * @see valkey.io for details. - * @param keysAndIds A Map of keys and entry ids to read from. The - * Map is composed of a stream's key and the id of the entry after which the stream - * will be read. + * @param keysAndIds A Map of keys and entry IDs to read from. * @param options Options detailing how to read the stream {@link StreamReadOptions}. - * @return A {@literal Map>} with stream - * keys, to Map of stream-ids, to an array of pairings with format [[field, entry], [field, entry], ...]. + * @return A {@literal Map>} with stream keys, to + * Map of stream entry IDs, to an array of pairings with format + * [[field, entry], [field, entry], ...]. * @example *
      {@code
            * // retrieve streamKey entries and block for 1 second if is no stream data
      @@ -181,9 +267,10 @@ CompletableFuture>> xreadBina
            *     System.out.printf("Key: %s", keyEntry.getKey());
            *     for (var streamEntry : keyEntry.getValue().entrySet()) {
            *         Arrays.stream(streamEntry.getValue()).forEach(entity ->
      -     *             System.out.printf("stream id: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
      +     *             System.out.printf("stream entry ID: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
            *         );
            *     }
      +     * }
            * }
      */ CompletableFuture>> xread( @@ -195,12 +282,12 @@ CompletableFuture>> xread( * @apiNote When in cluster mode, all keys in keysAndIds must map to the same hash * slot. * @see valkey.io for details. - * @param keysAndIds A Map of keys and entry ids to read from. The - * Map is composed of a stream's key and the id of the entry after which the stream - * will be read. + * @param keysAndIds A Map of keys and entry IDs to read from. * @param options Options detailing how to read the stream {@link StreamReadOptions}. - * @return A {@literal Map>} with stream - * keys, to Map of stream-ids, to an array of pairings with format [[field, entry], [field, entry], ...]. + * @return A {@literal Map>} with + * stream keys, to Map of stream entry IDs, to an array of pairings with format + * + * [[field, entry], [field, entry], ...]. * @example *
      {@code
            * // retrieve streamKey entries and block for 1 second if is no stream data
      @@ -211,9 +298,10 @@ CompletableFuture>> xread(
            *     System.out.printf("Key: %s", keyEntry.getKey());
            *     for (var streamEntry : keyEntry.getValue().entrySet()) {
            *         Arrays.stream(streamEntry.getValue()).forEach(entity ->
      -     *             System.out.printf("stream id: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
      +     *             System.out.printf("stream entry ID: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
            *         );
            *     }
      +     * }
            * }
      */ CompletableFuture>> xreadBinary( @@ -328,34 +416,36 @@ CompletableFuture>> xreadBina * * @see valkey.io for details. * @param key The key of the stream. - * @param start Starting stream ID bound for range. + * @param start Starting stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
      * - * @param end Ending stream ID bound for range. + * @param end Ending stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
      * - * @return A Map of key to stream entry data, where entry data is an array of pairings with format [[field, entry], [field, entry], ...]. + * @return A Map of key to stream entry data, where entry data is an array of + * pairings with format [[field, entry], [field, entry], ...]. Returns or + * null if count is non-positive. * @example *
      {@code
            * // Retrieve all stream entries
            * Map result = client.xrange("key", InfRangeBound.MIN, InfRangeBound.MAX).get();
            * result.forEach((k, v) -> {
      -     *     System.out.println("Stream ID: " + k);
      +     *     System.out.println("stream entry ID: " + k);
            *     for (int i = 0; i < v.length; i++) {
            *         System.out.println(v[i][0] + ": " + v[i][1]);
            *     }
            * });
            * // Retrieve exactly one stream entry by id
            * Map result = client.xrange("key", IdBound.of(streamId), IdBound.of(streamId)).get();
      -     * System.out.println("Stream ID: " + streamid + " -> " + Arrays.toString(result.get(streamid)));
      +     * System.out.println("stream entry ID: " + streamid + " -> " + Arrays.toString(result.get(streamid)));
            * }
      */ CompletableFuture> xrange(String key, StreamRange start, StreamRange end); @@ -365,34 +455,36 @@ CompletableFuture>> xreadBina * * @see valkey.io for details. * @param key The key of the stream. - * @param start Starting stream ID bound for range. + * @param start Starting stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
      * - * @param end Ending stream ID bound for range. + * @param end Ending stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
      * - * @return A Map of key to stream entry data, where entry data is an array of pairings with format [[field, entry], [field, entry], ...]. + * @return A Map of key to stream entry data, where entry data is an array of + * pairings with format [[field, entry], [field, entry], ...]. Returns or + * null if count is non-positive. * @example *
      {@code
            * // Retrieve all stream entries
            * Map result = client.xrange(gs("key"), InfRangeBound.MIN, InfRangeBound.MAX).get();
            * result.forEach((k, v) -> {
      -     *     System.out.println("Stream ID: " + k);
      +     *     System.out.println("stream entry ID: " + k);
            *     for (int i = 0; i < v.length; i++) {
            *         System.out.println(v[i][0] + ": " + v[i][1]);
            *     }
            * });
            * // Retrieve exactly one stream entry by id
            * Map result = client.xrange(gs("key"), IdBound.of(streamId), IdBound.of(streamId)).get();
      -     * System.out.println("Stream ID: " + streamid + " -> " + Arrays.toString(result.get(streamid)));
      +     * System.out.println("stream entry ID: " + streamid + " -> " + Arrays.toString(result.get(streamid)));
            * }
      */ CompletableFuture> xrange( @@ -403,28 +495,30 @@ CompletableFuture> xrange( * * @see valkey.io for details. * @param key The key of the stream. - * @param start Starting stream ID bound for range. + * @param start Starting stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
      * - * @param end Ending stream ID bound for range. + * @param end Ending stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
      * * @param count Maximum count of stream entries to return. - * @return A Map of key to stream entry data, where entry data is an array of pairings with format [[field, entry], [field, entry], ...]. + * @return A Map of key to stream entry data, where entry data is an array of + * pairings with format [[field, entry], [field, entry], ...]. Returns or + * null if count is non-positive. * @example *
      {@code
            * // Retrieve the first 2 stream entries
            * Map result = client.xrange("key", InfRangeBound.MIN, InfRangeBound.MAX, 2).get();
            * result.forEach((k, v) -> {
      -     *     System.out.println("Stream ID: " + k);
      +     *     System.out.println("stream entry ID: " + k);
            *     for (int i = 0; i < v.length; i++) {
            *         System.out.println(v[i][0] + ": " + v[i][1]);
            *     }
      @@ -439,28 +533,30 @@ CompletableFuture> xrange(
            *
            * @see valkey.io for details.
            * @param key The key of the stream.
      -     * @param start Starting stream ID bound for range.
      +     * @param start Starting stream entry ID bound for range.
            *     
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
      * - * @param end Ending stream ID bound for range. + * @param end Ending stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
      * * @param count Maximum count of stream entries to return. - * @return A Map of key to stream entry data, where entry data is an array of pairings with format [[field, entry], [field, entry], ...]. + * @return A Map of key to stream entry data, where entry data is an array of + * pairings with format [[field, entry], [field, entry], ...]. Returns or + * null if count is non-positive. * @example *
      {@code
            * // Retrieve the first 2 stream entries
            * Map result = client.xrange(gs("key"), InfRangeBound.MIN, InfRangeBound.MAX, 2).get();
            * result.forEach((k, v) -> {
      -     *     System.out.println("Stream ID: " + k);
      +     *     System.out.println("stream entry ID: " + k);
            *     for (int i = 0; i < v.length; i++) {
            *         System.out.println(v[i][0] + ": " + v[i][1]);
            *     }
      @@ -477,34 +573,36 @@ CompletableFuture> xrange(
            *
            * @see valkey.io for details.
            * @param key The key of the stream.
      -     * @param end Ending stream ID bound for range.
      +     * @param end Ending stream entry ID bound for range.
            *     
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
      * - * @param start Starting stream ID bound for range. + * @param start Starting stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
      * - * @return A Map of key to stream entry data, where entry data is an array of pairings with format [[field, entry], [field, entry], ...]. + * @return A Map of key to stream entry data, where entry data is an array of + * pairings with format [[field, entry], [field, entry], ...]. Returns or + * null if count is non-positive. * @example *
      {@code
            * // Retrieve all stream entries
            * Map result = client.xrevrange("key", InfRangeBound.MAX, InfRangeBound.MIN).get();
            * result.forEach((k, v) -> {
      -     *     System.out.println("Stream ID: " + k);
      +     *     System.out.println("stream entry ID: " + k);
            *     for (int i = 0; i < v.length; i++) {
            *         System.out.println(v[i][0] + ": " + v[i][1]);
            *     }
            * });
            * // Retrieve exactly one stream entry by id
            * Map result = client.xrevrange("key", IdBound.of(streamId), IdBound.of(streamId)).get();
      -     * System.out.println("Stream ID: " + streamid + " -> " + Arrays.toString(result.get(streamid)));
      +     * System.out.println("stream entry ID: " + streamid + " -> " + Arrays.toString(result.get(streamid)));
            * }
      */ CompletableFuture> xrevrange( @@ -517,34 +615,35 @@ CompletableFuture> xrevrange( * * @see valkey.io for details. * @param key The key of the stream. - * @param end Ending stream ID bound for range. + * @param end Ending stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
      * - * @param start Starting stream ID bound for range. + * @param start Starting stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
      * - * @return A Map of key to stream entry data, where entry data is an array of pairings with format [[field, entry], [field, entry], ...]. + * @return A Map of key to stream entry data, where entry data is an array of + * pairings with format [[field, entry], [field, entry], ...]. * @example *
      {@code
            * // Retrieve all stream entries
            * Map result = client.xrevrange(gs("key"), InfRangeBound.MAX, InfRangeBound.MIN).get();
            * result.forEach((k, v) -> {
      -     *     System.out.println("Stream ID: " + k);
      +     *     System.out.println("stream entry ID: " + k);
            *     for (int i = 0; i < v.length; i++) {
            *         System.out.println(v[i][0] + ": " + v[i][1]);
            *     }
            * });
            * // Retrieve exactly one stream entry by id
            * Map result = client.xrevrange(gs("key"), IdBound.of(streamId), IdBound.of(streamId)).get();
      -     * System.out.println("Stream ID: " + streamid + " -> " + Arrays.toString(result.get(streamid)));
      +     * System.out.println("stream entry ID: " + streamid + " -> " + Arrays.toString(result.get(streamid)));
            * }
      */ CompletableFuture> xrevrange( @@ -557,28 +656,30 @@ CompletableFuture> xrevrange( * * @see valkey.io for details. * @param key The key of the stream. - * @param end Ending stream ID bound for range. + * @param end Ending stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
      * - * @param start Starting stream ID bound for range. + * @param start Starting stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
      * * @param count Maximum count of stream entries to return. - * @return A Map of key to stream entry data, where entry data is an array of pairings with format [[field, entry], [field, entry], ...]. + * @return A Map of key to stream entry data, where entry data is an array of + * pairings with format [[field, entry], [field, entry], ...]. Returns or + * null if count is non-positive. * @example *
      {@code
            * // Retrieve the first 2 stream entries
            * Map result = client.xrange("key", InfRangeBound.MAX, InfRangeBound.MIN, 2).get();
            * result.forEach((k, v) -> {
      -     *     System.out.println("Stream ID: " + k);
      +     *     System.out.println("stream entry ID: " + k);
            *     for (int i = 0; i < v.length; i++) {
            *         System.out.println(v[i][0] + ": " + v[i][1]);
            *     }
      @@ -590,33 +691,35 @@ CompletableFuture> xrevrange(
       
           /**
            * Returns stream entries matching a given range of IDs in reverse order.
      - * Equivalent to {@link #xrange(GlideString, StreamRange, StreamRange, long)} but returns the entries - * in reverse order. + * Equivalent to {@link #xrange(GlideString, StreamRange, StreamRange, long)} but returns the + * entries in reverse order. * * @see valkey.io for details. * @param key The key of the stream. - * @param end Ending stream ID bound for range. + * @param end Ending stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
      * - * @param start Starting stream ID bound for range. + * @param start Starting stream entry ID bound for range. *
        - *
      • Use {@link IdBound#of} to specify a stream ID. - *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
      • Use {@link IdBound#of} to specify a stream entry ID. + *
      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
      • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
      * * @param count Maximum count of stream entries to return. - * @return A Map of key to stream entry data, where entry data is an array of pairings with format [[field, entry], [field, entry], ...]. + * @return A Map of key to stream entry data, where entry data is an array of + * pairings with format [[field, entry], [field, entry], ...]. Returns or + * null if count is non-positive. * @example *
      {@code
            * // Retrieve the first 2 stream entries
            * Map result = client.xrange(gs("key"), InfRangeBound.MAX, InfRangeBound.MIN, 2).get();
            * result.forEach((k, v) -> {
      -     *     System.out.println("Stream ID: " + k);
      +     *     System.out.println("stream entry ID: " + k);
            *     for (int i = 0; i < v.length; i++) {
            *         System.out.println(v[i][0] + ": " + v[i][1]);
            *     }
      @@ -887,17 +990,17 @@ CompletableFuture xgroupSetId(
            * @apiNote When in cluster mode, all keys in keysAndIds must map to the same hash
            *     slot.
            * @see valkey.io for details.
      -     * @param keysAndIds A Map of keys and entry ids to read from. The 
      -     *     Map is composed of a stream's key and the id of the entry after which the stream
      -     *     will be read. Use the special id of {@literal ">"} to receive only new messages.
      +     * @param keysAndIds A Map of keys and entry IDs to read from.
      + * Use the special ID of {@literal ">"} to receive only new messages. * @param group The consumer group name. * @param consumer The consumer name. - * @return A {@literal Map>} with stream - * keys, to Map of stream-ids, to an array of pairings with format [[field, entry], [field, entry], ...]. - * Returns null if there is no stream that can be served. + * @return A {@literal Map>} with stream keys, to + * Map of stream entry IDs, to an array of pairings with format + * [[field, entry], [field, entry], ...]. Returns null if there is no + * stream that can be served. * @example *
      {@code
      -     * // create a new stream at "mystream", with stream id "1-0"
      +     * // create a new stream at "mystream", with stream entry ID "1-0"
            * String streamId = client.xadd("mystream", Map.of("myfield", "mydata"), StreamAddOptions.builder().id("1-0").build()).get();
            * assert client.xgroupCreate("mystream", "mygroup", "0-0").get().equals("OK"); // create the consumer group "mygroup"
            * Map> streamReadResponse = client.xreadgroup(Map.of("mystream", ">"), "mygroup", "myconsumer").get();
      @@ -906,11 +1009,11 @@ CompletableFuture xgroupSetId(
            *     System.out.printf("Key: %s", keyEntry.getKey());
            *     for (var streamEntry : keyEntry.getValue().entrySet()) {
            *         Arrays.stream(streamEntry.getValue()).forEach(entity ->
      -     *             System.out.printf("stream id: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
      +     *             System.out.printf("stream entry ID: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
            *         );
            *     }
            * }
      -     * 
      + * }
      */ CompletableFuture>> xreadgroup( Map keysAndIds, String group, String consumer); @@ -921,17 +1024,18 @@ CompletableFuture>> xreadgroup( * @apiNote When in cluster mode, all keys in keysAndIds must map to the same hash * slot. * @see valkey.io for details. - * @param keysAndIds A Map of keys and entry ids to read from. The - * Map is composed of a stream's key and the id of the entry after which the stream - * will be read. Use the special id of {@literal gs(">")} to receive only new messages. + * @param keysAndIds A Map of keys and entry IDs to read from.
      + * Use the special ID of {@literal gs(">")} to receive only new messages. * @param group The consumer group name. * @param consumer The consumer name. - * @return A {@literal Map>} with stream - * keys, to Map of stream-ids, to an array of pairings with format [[field, entry], [field, entry], ...]. - * Returns null if there is no stream that can be served. + * @return A {@literal Map>} with + * stream keys, to Map of stream entry IDs, to an array of pairings with format + * + * [[field, entry], [field, entry], ...]. Returns null if there is no + * stream that can be served. * @example *
      {@code
      -     * // create a new stream at gs("mystream"), with stream id gs("1-0")
      +     * // create a new stream at gs("mystream"), with stream entry ID gs("1-0")
            * String streamId = client.xadd(gs("mystream"), Map.of(gs("myfield"), gs("mydata")), StreamAddOptionsBinary.builder().id(gs("1-0")).build()).get();
            * assert client.xgroupCreate(gs("mystream"), gs("mygroup"), gs("0-0")).get().equals("OK"); // create the consumer group gs("mygroup")
            * Map> streamReadResponse = client.xreadgroup(Map.of(gs("mystream"), gs(">")), gs("mygroup"), gs("myconsumer")).get();
      @@ -940,11 +1044,11 @@ CompletableFuture>> xreadgroup(
            *     System.out.printf("Key: %s", keyEntry.getKey());
            *     for (var streamEntry : keyEntry.getValue().entrySet()) {
            *         Arrays.stream(streamEntry.getValue()).forEach(entity ->
      -     *             System.out.printf("stream id: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
      +     *             System.out.printf("stream entry ID: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
            *         );
            *     }
            * }
      -     * 
      + * }
      */ CompletableFuture>> xreadgroup( Map keysAndIds, GlideString group, GlideString consumer); @@ -955,18 +1059,18 @@ CompletableFuture>> xreadgrou * @apiNote When in cluster mode, all keys in keysAndIds must map to the same hash * slot. * @see valkey.io for details. - * @param keysAndIds A Map of keys and entry ids to read from. The - * Map is composed of a stream's key and the id of the entry after which the stream - * will be read. Use the special id of {@literal ">"} to receive only new messages. + * @param keysAndIds A Map of keys and entry IDs to read from.
      + * Use the special ID of {@literal ">"} to receive only new messages. * @param group The consumer group name. * @param consumer The consumer name. * @param options Options detailing how to read the stream {@link StreamReadGroupOptions}. - * @return A {@literal Map>} with stream - * keys, to Map of stream-ids, to an array of pairings with format [[field, entry], [field, entry], ...]. - * Returns null if there is no stream that can be served. + * @return A {@literal Map>} with stream keys, to + * Map of stream entry IDs, to an array of pairings with format + * [[field, entry], [field, entry], ...]. Returns null if there is no + * stream that can be served. * @example *
      {@code
      -     * // create a new stream at "mystream", with stream id "1-0"
      +     * // create a new stream at "mystream", with stream entry ID "1-0"
            * String streamId = client.xadd("mystream", Map.of("myfield", "mydata"), StreamAddOptions.builder().id("1-0").build()).get();
            * assert client.xgroupCreate("mystream", "mygroup", "0-0").get().equals("OK"); // create the consumer group "mygroup"
            * StreamReadGroupOptions options = StreamReadGroupOptions.builder().count(1).build(); // retrieves only a single message at a time
      @@ -976,11 +1080,11 @@ CompletableFuture>> xreadgrou
            *     System.out.printf("Key: %s", keyEntry.getKey());
            *     for (var streamEntry : keyEntry.getValue().entrySet()) {
            *         Arrays.stream(streamEntry.getValue()).forEach(entity ->
      -     *             System.out.printf("stream id: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
      +     *             System.out.printf("stream entry ID: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
            *         );
            *     }
            * }
      -     * 
      + * }
      */ CompletableFuture>> xreadgroup( Map keysAndIds, @@ -994,18 +1098,19 @@ CompletableFuture>> xreadgroup( * @apiNote When in cluster mode, all keys in keysAndIds must map to the same hash * slot. * @see valkey.io for details. - * @param keysAndIds A Map of keys and entry ids to read from. The - * Map is composed of a stream's key and the id of the entry after which the stream - * will be read. Use the special id of {@literal gs(">")} to receive only new messages. + * @param keysAndIds A Map of keys and entry IDs to read from.
      + * Use the special ID of {@literal gs(">")} to receive only new messages. * @param group The consumer group name. * @param consumer The consumer name. * @param options Options detailing how to read the stream {@link StreamReadGroupOptions}. - * @return A {@literal Map>} with stream - * keys, to Map of stream-ids, to an array of pairings with format [[field, entry], [field, entry], ...]. - * Returns null if there is no stream that can be served. + * @return A {@literal Map>} with + * stream keys, to Map of stream entry IDs, to an array of pairings with format + * + * [[field, entry], [field, entry], ...]. Returns null if there is no + * stream that can be served. * @example *
      {@code
      -     * // create a new stream at gs("mystream"), with stream id gs("1-0")
      +     * // create a new stream at gs("mystream"), with stream entry ID gs("1-0")
            * String streamId = client.xadd(gs("mystream"), Map.of(gs("myfield"), gs("mydata")), StreamAddOptionsBinary.builder().id(gs("1-0")).build()).get();
            * assert client.xgroupCreate(gs("mystream"), gs("mygroup"), gs("0-0")).get().equals("OK"); // create the consumer group gs("mygroup")
            * StreamReadGroupOptions options = StreamReadGroupOptions.builder().count(1).build(); // retrieves only a single message at a time
      @@ -1015,11 +1120,11 @@ CompletableFuture>> xreadgroup(
            *     System.out.printf("Key: %s", keyEntry.getKey());
            *     for (var streamEntry : keyEntry.getValue().entrySet()) {
            *         Arrays.stream(streamEntry.getValue()).forEach(entity ->
      -     *             System.out.printf("stream id: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
      +     *             System.out.printf("stream entry ID: %s; field: %s; value: %s\n", streamEntry.getKey(), entity[0], entity[1])
            *         );
            *     }
            * }
      -     * 
      + * }
      */ CompletableFuture>> xreadgroup( Map keysAndIds, @@ -1028,8 +1133,9 @@ CompletableFuture>> xreadgrou StreamReadGroupOptions options); /** - * Returns the number of messages that were successfully acknowledged by the consumer group member of a stream. - * This command should be called on a pending message so that such message does not get processed again. + * Returns the number of messages that were successfully acknowledged by the consumer group member + * of a stream. This command should be called on a pending message so that such message does not + * get processed again. * * @see valkey.io for details. * @param key The key of the stream. @@ -1043,13 +1149,14 @@ CompletableFuture>> xreadgrou * var readResult = client.xreadgroup(Map.of("mystream", entryId), "mygroup", "my0consumer").get(); * // acknowledge messages on stream * assert 1L == client.xack("mystream", "mygroup", new String[] {entryId}).get(); - *
+ * }
*/ CompletableFuture xack(String key, String group, String[] ids); /** - * Returns the number of messages that were successfully acknowledged by the consumer group member of a stream. - * This command should be called on a pending message so that such message does not get processed again. + * Returns the number of messages that were successfully acknowledged by the consumer group member + * of a stream. This command should be called on a pending message so that such message does not + * get processed again. * * @param key The key of the stream. * @param group The consumer group name. @@ -1062,7 +1169,7 @@ CompletableFuture>> xreadgrou * var readResult = client.xreadgroup(Map.of(gs("mystream"), entryId), gs("mygroup"), gs("my0consumer")).get(); * // acknowledge messages on stream * assert 1L == client.xack(gs("mystream"), gs("mygroup"), new GlideString[] {entryId}).get(); - *
+ * }
*/ CompletableFuture xack(GlideString key, GlideString group, GlideString[] ids); @@ -1073,22 +1180,26 @@ CompletableFuture>> xreadgrou * @param key The key of the stream. * @param group The consumer group name. * @return An array that includes the summary of pending messages, with the format - * [NumOfMessages, StartId, EndId, [Consumer, NumOfMessages]], where: - *
    - *
  • NumOfMessages: The total number of pending messages for this consumer group. - *
  • StartId: The smallest ID among the pending messages. - *
  • EndId: The greatest ID among the pending messages. - *
  • [[Consumer, NumOfMessages], ...]: A 2D-array of every consumer - * in the consumer group with at least one pending message, and the number of pending messages it has. - *
- * @example - *
{@code
+     *     [NumOfMessages, StartId, EndId, [Consumer, NumOfMessages]], where:
+     *     
    + *
  • NumOfMessages: The total number of pending messages for this consumer + * group. + *
  • StartId: The smallest ID among the pending messages. + *
  • EndId: The greatest ID among the pending messages. + *
  • [[Consumer, NumOfMessages], ...]: A 2D-array of every + * consumer in the consumer group with at least one pending message, and the number of + * pending messages it has. + *
+ * + * @example + *
{@code
      * // Retrieve a summary of all pending messages from key "my_stream"
      * Object[] result = client.xpending("my_stream", "my_group").get();
      * System.out.println("Number of pending messages: " + result[0]);
      * System.out.println("Start and End ID of messages: [" + result[1] + ", " + result[2] + "]");
      * for (Object[] consumerResult : (Object[][]) result[3]) {
      *     System.out.println("Number of Consumer messages: [" + consumerResult[0] + ", " + consumerResult[1] + "]");
+     * }
      * }
*/ CompletableFuture xpending(String key, String group); @@ -1100,135 +1211,156 @@ CompletableFuture>> xreadgrou * @param key The key of the stream. * @param group The consumer group name. * @return An array that includes the summary of pending messages, with the format - * [NumOfMessages, StartId, EndId, [Consumer, NumOfMessages]], where: - *
    - *
  • NumOfMessages: The total number of pending messages for this consumer group. - *
  • StartId: The smallest ID among the pending messages. - *
  • EndId: The greatest ID among the pending messages. - *
  • [[Consumer, NumOfMessages], ...]: A 2D-array of every consumer - * in the consumer group with at least one pending message, and the number of pending messages it has. - *
- * @example - *
{@code
+     *     [NumOfMessages, StartId, EndId, [Consumer, NumOfMessages]], where:
+     *     
    + *
  • NumOfMessages: The total number of pending messages for this consumer + * group. + *
  • StartId: The smallest ID among the pending messages. + *
  • EndId: The greatest ID among the pending messages. + *
  • [[Consumer, NumOfMessages], ...]: A 2D-array of every + * consumer in the consumer group with at least one pending message, and the number of + * pending messages it has. + *
+ * + * @example + *
{@code
      * // Retrieve a summary of all pending messages from key "my_stream"
      * Object[] result = client.xpending(gs("my_stream"), gs("my_group")).get();
      * System.out.println("Number of pending messages: " + result[0]);
      * System.out.println("Start and End ID of messages: [" + result[1] + ", " + result[2] + "]");
      * for (Object[] consumerResult : (Object[][]) result[3]) {
      *     System.out.println("Number of Consumer messages: [" + consumerResult[0] + ", " + consumerResult[1] + "]");
+     * }
      * }
*/ CompletableFuture xpending(GlideString key, GlideString group); /** - * Returns an extended form of stream message information for pending messages matching a given range of IDs. + * Returns an extended form of stream message information for pending messages matching a given + * range of IDs. * * @see valkey.io for details. * @param key The key of the stream. * @param group The consumer group name. - * @param start Starting stream ID bound for range. + * @param start Starting stream entry ID bound for range. *
    - *
  • Use {@link IdBound#of} to specify a stream ID. - *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
  • Use {@link IdBound#of} to specify a stream entry ID. + *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
  • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
* - * @param end Ending stream ID bound for range. + * @param end Ending stream entry ID bound for range. *
    - *
  • Use {@link IdBound#of} to specify a stream ID. - *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
  • Use {@link IdBound#of} to specify a stream entry ID. + *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
  • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
+ * * @param count Limits the number of messages returned. - * @return A 2D-array of 4-tuples containing extended message information with the format - * [[ID, Consumer, TimeElapsed, NumOfDelivered], ... ], where: - *
    - *
  • ID: The ID of the message. - *
  • Consumer: The name of the consumer that fetched the message and has still to acknowledge it. We call it the current owner of the message. - *
  • TimeElapsed: The number of milliseconds that elapsed since the last time this message was delivered to this consumer. - *
  • NumOfDelivered: The number of times this message was delivered. - *
- * @example - *
{@code
+     * @return A 2D-array of 4-tuples containing extended message information with the
+     *     format [[ID, Consumer, TimeElapsed, NumOfDelivered], ... ], where:
+     *     
    + *
  • ID: The ID of the message. + *
  • Consumer: The name of the consumer that fetched the message and has + * still to acknowledge it. We call it the current owner of the message. + *
  • TimeElapsed: The number of milliseconds that elapsed since the last time + * this message was delivered to this consumer. + *
  • NumOfDelivered: The number of times this message was delivered. + *
+ * + * @example + *
{@code
      * // Retrieve up to 10 pending messages from key "my_stream" in extended form
      * Object[][] result = client.xpending("my_stream", "my_group", InfRangeBound.MIN, InfRangeBound.MAX, 10L).get();
      * for (Object[] messageResult : result) {
      *     System.out.printf("Message %s from consumer %s was read %s times", messageResult[0], messageResult[1], messageResult[2]);
+     * }
      * }
*/ CompletableFuture xpending( String key, String group, StreamRange start, StreamRange end, long count); /** - * Returns an extended form of stream message information for pending messages matching a given range of IDs. + * Returns an extended form of stream message information for pending messages matching a given + * range of IDs. * * @see valkey.io for details. * @param key The key of the stream. * @param group The consumer group name. - * @param start Starting stream ID bound for range. + * @param start Starting stream entry ID bound for range. *
    - *
  • Use {@link IdBound#of} to specify a stream ID. - *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
  • Use {@link IdBound#of} to specify a stream entry ID. + *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
  • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
* - * @param end Ending stream ID bound for range. + * @param end Ending stream entry ID bound for range. *
    - *
  • Use {@link IdBound#of} to specify a stream ID. - *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
  • Use {@link IdBound#of} to specify a stream entry ID. + *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
  • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
+ * * @param count Limits the number of messages returned. - * @return A 2D-array of 4-tuples containing extended message information with the format - * [[ID, Consumer, TimeElapsed, NumOfDelivered], ... ], where: - *
    - *
  • ID: The ID of the message. - *
  • Consumer: The name of the consumer that fetched the message and has still to acknowledge it. We call it the current owner of the message. - *
  • TimeElapsed: The number of milliseconds that elapsed since the last time this message was delivered to this consumer. - *
  • NumOfDelivered: The number of times this message was delivered. - *
- * @example - *
{@code
+     * @return A 2D-array of 4-tuples containing extended message information with the
+     *     format [[ID, Consumer, TimeElapsed, NumOfDelivered], ... ], where:
+     *     
    + *
  • ID: The ID of the message. + *
  • Consumer: The name of the consumer that fetched the message and has + * still to acknowledge it. We call it the current owner of the message. + *
  • TimeElapsed: The number of milliseconds that elapsed since the last time + * this message was delivered to this consumer. + *
  • NumOfDelivered: The number of times this message was delivered. + *
+ * + * @example + *
{@code
      * // Retrieve up to 10 pending messages from key "my_stream" in extended form
      * Object[][] result = client.xpending(gs("my_stream"), gs("my_group"), InfRangeBound.MIN, InfRangeBound.MAX, 10L).get();
      * for (Object[] messageResult : result) {
      *     System.out.printf("Message %s from consumer %s was read %s times", messageResult[0], messageResult[1], messageResult[2]);
+     * }
      * }
*/ CompletableFuture xpending( GlideString key, GlideString group, StreamRange start, StreamRange end, long count); /** - * Returns an extended form of stream message information for pending messages matching a given range of IDs. + * Returns an extended form of stream message information for pending messages matching a given + * range of IDs. * * @see valkey.io for details. * @param key The key of the stream. * @param group The consumer group name. - * @param start Starting stream ID bound for range. + * @param start Starting stream entry ID bound for range. *
    - *
  • Use {@link IdBound#of} to specify a stream ID. - *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
  • Use {@link IdBound#of} to specify a stream entry ID. + *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
  • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
* - * @param end Ending stream ID bound for range. + * @param end Ending stream entry ID bound for range. *
    - *
  • Use {@link IdBound#of} to specify a stream ID. - *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
  • Use {@link IdBound#of} to specify a stream entry ID. + *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
  • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
+ * * @param count Limits the number of messages returned. * @param options Stream add options {@link StreamPendingOptions}. - * @return A 2D-array of 4-tuples containing extended message information with the format - * [[ID, Consumer, TimeElapsed, NumOfDelivered], ... ], where: - *
    - *
  • ID: The ID of the message. - *
  • Consumer: The name of the consumer that fetched the message and has still to acknowledge it. We call it the current owner of the message. - *
  • TimeElapsed: The number of milliseconds that elapsed since the last time this message was delivered to this consumer. - *
  • NumOfDelivered: The number of times this message was delivered. - *
- * @example - *
{@code
+     * @return A 2D-array of 4-tuples containing extended message information with the
+     *     format [[ID, Consumer, TimeElapsed, NumOfDelivered], ... ], where:
+     *     
    + *
  • ID: The ID of the message. + *
  • Consumer: The name of the consumer that fetched the message and has + * still to acknowledge it. We call it the current owner of the message. + *
  • TimeElapsed: The number of milliseconds that elapsed since the last time + * this message was delivered to this consumer. + *
  • NumOfDelivered: The number of times this message was delivered. + *
+ * + * @example + *
{@code
      * // Retrieve up to 10 pending messages from key "my_stream" and consumer "my_consumer" in extended form
      * Object[][] result = client.xpending(
      *     "my_stream",
@@ -1240,6 +1372,7 @@ CompletableFuture xpending(
      * ).get();
      * for (Object[] messageResult : result) {
      *     System.out.printf("Message %s from consumer %s was read %s times", messageResult[0], messageResult[1], messageResult[2]);
+     * }
      * }
*/ CompletableFuture xpending( @@ -1251,36 +1384,41 @@ CompletableFuture xpending( StreamPendingOptions options); /** - * Returns an extended form of stream message information for pending messages matching a given range of IDs. + * Returns an extended form of stream message information for pending messages matching a given + * range of IDs. * * @see valkey.io for details. * @param key The key of the stream. * @param group The consumer group name. - * @param start Starting stream ID bound for range. + * @param start Starting stream entry ID bound for range. *
    - *
  • Use {@link IdBound#of} to specify a stream ID. - *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
  • Use {@link IdBound#of} to specify a stream entry ID. + *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
  • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
* - * @param end Ending stream ID bound for range. + * @param end Ending stream entry ID bound for range. *
    - *
  • Use {@link IdBound#of} to specify a stream ID. - *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
  • Use {@link IdBound#of} to specify a stream entry ID. + *
  • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry ID. *
  • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
+ * * @param count Limits the number of messages returned. * @param options Stream add options {@link StreamPendingOptionsBinary}. - * @return A 2D-array of 4-tuples containing extended message information with the format - * [[ID, Consumer, TimeElapsed, NumOfDelivered], ... ], where: - *
    - *
  • ID: The ID of the message. - *
  • Consumer: The name of the consumer that fetched the message and has still to acknowledge it. We call it the current owner of the message. - *
  • TimeElapsed: The number of milliseconds that elapsed since the last time this message was delivered to this consumer. - *
  • NumOfDelivered: The number of times this message was delivered. - *
- * @example - *
{@code
+     * @return A 2D-array of 4-tuples containing extended message information with the
+     *     format [[ID, Consumer, TimeElapsed, NumOfDelivered], ... ], where:
+     *     
    + *
  • ID: The ID of the message. + *
  • Consumer: The name of the consumer that fetched the message and has + * still to acknowledge it. We call it the current owner of the message. + *
  • TimeElapsed: The number of milliseconds that elapsed since the last time + * this message was delivered to this consumer. + *
  • NumOfDelivered: The number of times this message was delivered. + *
+ * + * @example + *
{@code
      * // Retrieve up to 10 pending messages from key "my_stream" and consumer "my_consumer" in extended form
      * Object[][] result = client.xpending(
      *     gs("my_stream"),
@@ -1292,6 +1430,7 @@ CompletableFuture xpending(
      * ).get();
      * for (Object[] messageResult : result) {
      *     System.out.printf("Message %s from consumer %s was read %s times", messageResult[0], messageResult[1], messageResult[2]);
+     * }
      * }
*/ CompletableFuture xpending( @@ -1314,7 +1453,7 @@ CompletableFuture xpending( * @return A Map of message entries with the format * {"entryId": [["entry", "data"], ...], ...} that are claimed by the consumer. * @example - *
+     *     
{@code
      * // read messages from streamId for consumer1
      * var readResult = client.xreadgroup(Map.of("mystream", ">"), "mygroup", "consumer1").get();
      * // "entryId" is now read, and we can assign the pending messages to consumer2
@@ -1325,7 +1464,7 @@ CompletableFuture xpending(
      *         System.out.printf("{%s=%s}%n", entry[0], entry[1]);
      *     }
      * }
-     * 
+ * }
*/ CompletableFuture> xclaim( String key, String group, String consumer, long minIdleTime, String[] ids); @@ -1342,7 +1481,7 @@ CompletableFuture> xclaim( * @return A Map of message entries with the format * {"entryId": [["entry", "data"], ...], ...} that are claimed by the consumer. * @example - *
+     *     
{@code
      * // read messages from streamId for consumer1
      * var readResult = client.xreadgroup(Map.of(gs("mystream"), gs(">")), gs("mygroup"), gs("consumer1")).get();
      * // "entryId" is now read, and we can assign the pending messages to consumer2
@@ -1353,7 +1492,7 @@ CompletableFuture> xclaim(
      *         System.out.printf("{%s=%s}%n", entry[0], entry[1]);
      *     }
      * }
-     * 
+ * }
*/ CompletableFuture> xclaim( GlideString key, @@ -1375,7 +1514,7 @@ CompletableFuture> xclaim( * @return A Map of message entries with the format * {"entryId": [["entry", "data"], ...], ...} that are claimed by the consumer. * @example - *
+     *     
{@code
      * // assign (force) unread and unclaimed messages to consumer2
      * StreamClaimOptions options = StreamClaimOptions.builder().force().build();
      * Map results = client.xclaim("mystream", "mygroup", "consumer2", 0L, new String[] {entryId}, options).get();
@@ -1385,7 +1524,7 @@ CompletableFuture> xclaim(
      *         System.out.printf("{%s=%s}%n", entry[0], entry[1]);
      *     }
      * }
-     * 
+ * }
*/ CompletableFuture> xclaim( String key, @@ -1408,7 +1547,7 @@ CompletableFuture> xclaim( * @return A Map of message entries with the format * {"entryId": [["entry", "data"], ...], ...} that are claimed by the consumer. * @example - *
+     *     
{@code
      * // assign (force) unread and unclaimed messages to consumer2
      * StreamClaimOptions options = StreamClaimOptions.builder().force().build();
      * Map results = client.xclaim(gs("mystream"), gs("mygroup"), gs("consumer2"), 0L, new GlideString[] {entryId}, options).get();
@@ -1418,7 +1557,7 @@ CompletableFuture> xclaim(
      *         System.out.printf("{%s=%s}%n", entry[0], entry[1]);
      *     }
      * }
-     * 
+ * }
*/ CompletableFuture> xclaim( GlideString key, @@ -1440,7 +1579,7 @@ CompletableFuture> xclaim( * @param ids An array of entry ids. * @return An array of message ids claimed by the consumer. * @example - *
+     *     
{@code
      * // read messages from streamId for consumer1
      * var readResult = client.xreadgroup(Map.of("mystream", ">"), "mygroup", "consumer1").get();
      * // "entryId" is now read, and we can assign the pending messages to consumer2
@@ -1448,7 +1587,7 @@ CompletableFuture> xclaim(
      * for (String id: results) {
      *     System.out.printf("consumer2 claimed stream entry ID: %s %n", id);
      * }
-     * 
+ * }
*/ CompletableFuture xclaimJustId( String key, String group, String consumer, long minIdleTime, String[] ids); @@ -1465,7 +1604,7 @@ CompletableFuture xclaimJustId( * @param ids An array of entry ids. * @return An array of message ids claimed by the consumer. * @example - *
+     *     
{@code
      * // read messages from streamId for consumer1
      * var readResult = client.xreadgroup(Map.of(gs("mystream"), gs(">")), gs("mygroup"), gs("consumer1")).get();
      * // "entryId" is now read, and we can assign the pending messages to consumer2
@@ -1473,7 +1612,7 @@ CompletableFuture xclaimJustId(
      * for (GlideString id: results) {
      *     System.out.printf("consumer2 claimed stream entry ID: %s %n", id);
      * }
-     * 
+ * }
*/ CompletableFuture xclaimJustId( GlideString key, @@ -1495,13 +1634,14 @@ CompletableFuture xclaimJustId( * @param options Stream claim options {@link StreamClaimOptions}. * @return An array of message ids claimed by the consumer. * @example - *
+     *     
{@code
      * // assign (force) unread and unclaimed messages to consumer2
      * StreamClaimOptions options = StreamClaimOptions.builder().force().build();
      * String[] results = client.xclaimJustId("mystream", "mygroup", "consumer2", 0L, new String[] {entryId}, options).get();
      * for (String id: results) {
      *     System.out.printf("consumer2 claimed stream entry ID: %s %n", id);
      * }
+     * }
*/ CompletableFuture xclaimJustId( String key, @@ -1524,13 +1664,14 @@ CompletableFuture xclaimJustId( * @param options Stream claim options {@link StreamClaimOptions}. * @return An array of message ids claimed by the consumer. * @example - *
+     *     
{@code
      * // assign (force) unread and unclaimed messages to consumer2
      * StreamClaimOptions options = StreamClaimOptions.builder().force().build();
      * GlideString[] results = client.xclaimJustId(gs("mystream"), gs("mygroup"), gs("consumer2"), 0L, new GlideString[] {entryId}, options).get();
      * for (GlideString id: results) {
      *     System.out.printf("consumer2 claimed stream entry ID: %s %n", id);
      * }
+     * }
*/ CompletableFuture xclaimJustId( GlideString key, @@ -1647,7 +1788,8 @@ CompletableFuture[]> xinfoConsumers( * specified value. * @return An array containing the following elements: *
    - *
  • A stream ID to be used as the start argument for the next call to XAUTOCLAIM + *
  • A stream entry ID to be used as the start argument for the next call to + * XAUTOCLAIM * . This ID is equivalent to the next ID in the stream after the entries that * were scanned, or "0-0" if the entire stream was scanned. *
  • A mapping of the claimed entries, with the keys being the claimed entry IDs and the @@ -1681,7 +1823,8 @@ CompletableFuture xautoclaim( * specified value. * @return An array containing the following elements: *
      - *
    • A stream ID to be used as the start argument for the next call to XAUTOCLAIM + *
    • A stream entry ID to be used as the start argument for the next call to + * XAUTOCLAIM * . This ID is equivalent to the next ID in the stream after the entries that * were scanned, or "0-0" if the entire stream was scanned. *
    • A mapping of the claimed entries, with the keys being the claimed entry IDs and the @@ -1720,7 +1863,8 @@ CompletableFuture xautoclaim( * @param count Limits the number of claimed entries to the specified value. * @return An array containing the following elements: *
        - *
      • A stream ID to be used as the start argument for the next call to XAUTOCLAIM + *
      • A stream entry ID to be used as the start argument for the next call to + * XAUTOCLAIM * . This ID is equivalent to the next ID in the stream after the entries that * were scanned, or "0-0" if the entire stream was scanned. *
      • A mapping of the claimed entries, with the keys being the claimed entry IDs and the @@ -1755,7 +1899,8 @@ CompletableFuture xautoclaim( * @param count Limits the number of claimed entries to the specified value. * @return An array containing the following elements: *
          - *
        • A stream ID to be used as the start argument for the next call to XAUTOCLAIM + *
        • A stream entry ID to be used as the start argument for the next call to + * XAUTOCLAIM * . This ID is equivalent to the next ID in the stream after the entries that * were scanned, or "0-0" if the entire stream was scanned. *
        • A mapping of the claimed entries, with the keys being the claimed entry IDs and the @@ -1796,7 +1941,8 @@ CompletableFuture xautoclaim( * specified value. * @return An array containing the following elements: *
            - *
          • A stream ID to be used as the start argument for the next call to XAUTOCLAIM + *
          • A stream entry ID to be used as the start argument for the next call to + * XAUTOCLAIM * . This ID is equivalent to the next ID in the stream after the entries that * were scanned, or "0-0" if the entire stream was scanned. *
          • A list of the IDs for the claimed entries. @@ -1830,7 +1976,8 @@ CompletableFuture xautoclaimJustId( * specified value. * @return An array containing the following elements: *
              - *
            • A stream ID to be used as the start argument for the next call to XAUTOCLAIM + *
            • A stream entry ID to be used as the start argument for the next call to + * XAUTOCLAIM * . This ID is equivalent to the next ID in the stream after the entries that * were scanned, or "0-0" if the entire stream was scanned. *
            • A list of the IDs for the claimed entries. @@ -1869,7 +2016,8 @@ CompletableFuture xautoclaimJustId( * @param count Limits the number of claimed entries to the specified value. * @return An array containing the following elements: *
                - *
              • A stream ID to be used as the start argument for the next call to XAUTOCLAIM + *
              • A stream entry ID to be used as the start argument for the next call to + * XAUTOCLAIM * . This ID is equivalent to the next ID in the stream after the entries that * were scanned, or "0-0" if the entire stream was scanned. *
              • A list of the IDs for the claimed entries. @@ -1904,7 +2052,8 @@ CompletableFuture xautoclaimJustId( * @param count Limits the number of claimed entries to the specified value. * @return An array containing the following elements: *
                  - *
                • A stream ID to be used as the start argument for the next call to XAUTOCLAIM + *
                • A stream entry ID to be used as the start argument for the next call to + * XAUTOCLAIM * . This ID is equivalent to the next ID in the stream after the entries that * were scanned, or "0-0" if the entire stream was scanned. *
                • A list of the IDs for the claimed entries. diff --git a/java/client/src/main/java/glide/api/commands/StringBaseCommands.java b/java/client/src/main/java/glide/api/commands/StringBaseCommands.java index 38f6c50117..3f46f6a2cb 100644 --- a/java/client/src/main/java/glide/api/commands/StringBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/StringBaseCommands.java @@ -672,8 +672,8 @@ public interface StringBaseCommands { CompletableFuture append(GlideString key, GlideString value); /** - * Returns the longest common subsequence between strings stored at key1 and - * key2. + * Returns all the longest common subsequences combined between strings stored at key1 + * and key2. * * @since Valkey 7.0 and above. * @apiNote When in cluster mode, key1 and key2 must map to the same @@ -681,9 +681,9 @@ public interface StringBaseCommands { * @see valkey.io for details. * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. - * @return A String containing the longest common subsequence between the 2 strings. - * An empty String is returned if the keys do not exist or have no common - * subsequences. + * @return A String containing all the longest common subsequences combined between + * the 2 strings. An empty String is returned if the keys do not exist or have no + * common subsequences. * @example *
                  {@code
                        * // testKey1 = abcd, testKey2 = axcd
                  @@ -694,8 +694,8 @@ public interface StringBaseCommands {
                       CompletableFuture lcs(String key1, String key2);
                   
                       /**
                  -     * Returns the longest common subsequence between strings stored at key1 and 
                  -     * key2.
                  +     * Returns all the longest common subsequences combined between strings stored at key1
                  +     *  and key2.
                        *
                        * @since Valkey 7.0 and above.
                        * @apiNote When in cluster mode, key1 and key2 must map to the same
                  @@ -703,9 +703,9 @@ public interface StringBaseCommands {
                        * @see valkey.io for details.
                        * @param key1 The key that stores the first string.
                        * @param key2 The key that stores the second string.
                  -     * @return A String containing the longest common subsequence between the 2 strings.
                  -     *     An empty String is returned if the keys do not exist or have no common
                  -     *     subsequences.
                  +     * @return A String containing all the longest common subsequences combined between
                  +     *     the 2 strings. An empty GlideString is returned if the keys do not exist or
                  +     *     have no common subsequences.
                        * @example
                        *     
                  {@code
                        * // testKey1 = abcd, testKey2 = axcd
                  @@ -716,8 +716,8 @@ public interface StringBaseCommands {
                       CompletableFuture lcs(GlideString key1, GlideString key2);
                   
                       /**
                  -     * Returns the length of the longest common subsequence between strings stored at key1
                  -     *  and key2.
                  +     * Returns the total length of all the longest common subsequences between strings stored at
                  +     * key1 and key2.
                        *
                        * @since Valkey 7.0 and above.
                        * @apiNote When in cluster mode, key1 and key2 must map to the same
                  @@ -725,7 +725,7 @@ public interface StringBaseCommands {
                        * @see valkey.io for details.
                        * @param key1 The key that stores the first string.
                        * @param key2 The key that stores the second string.
                  -     * @return The length of the longest common subsequence between the 2 strings.
                  +     * @return The total length of all the longest common subsequences the 2 strings.
                        * @example
                        *     
                  {@code
                        * // testKey1 = abcd, testKey2 = axcd
                  @@ -736,8 +736,8 @@ public interface StringBaseCommands {
                       CompletableFuture lcsLen(String key1, String key2);
                   
                       /**
                  -     * Returns the length of the longest common subsequence between strings stored at key1
                  -     *  and key2.
                  +     * Returns the total length of all the longest common subsequences between strings stored at
                  +     * key1 and key2.
                        *
                        * @since Valkey 7.0 and above.
                        * @apiNote When in cluster mode, key1 and key2 must map to the same
                  @@ -756,8 +756,8 @@ public interface StringBaseCommands {
                       CompletableFuture lcsLen(GlideString key1, GlideString key2);
                   
                       /**
                  -     * Returns the indices and length of the longest common subsequence between strings stored at
                  -     * key1 and key2.
                  +     * Returns the indices and the total length of all the longest common subsequences between strings
                  +     * stored at key1 and key2.
                        *
                        * @since Valkey 7.0 and above.
                        * @apiNote When in cluster mode, key1 and key2 must map to the same
                  @@ -766,41 +766,41 @@ public interface StringBaseCommands {
                        * @param key1 The key that stores the first string.
                        * @param key2 The key that stores the second string.
                        * @return A Map containing the indices of the longest common subsequence between the
                  -     *     2 strings and the length of the longest common subsequence. The resulting map contains two
                  -     *     keys, "matches" and "len":
                  +     *     2 strings and the total length of all the longest common subsequences. The resulting map
                  +     *     contains two keys, "matches" and "len":
                        *     
                    - *
                  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. + *
                  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. *
                  • "matches" is mapped to a three dimensional Long array that stores pairs * of indices that represent the location of the common subsequences in the strings held * by key1 and key2. *
                  - * - * @example If key1 holds the string "abcd123" and key2 - * holds the string "bcdef123" then the sample result would be + * See example for more details. + * @example *
                  {@code
                  -     * new Long[][][] {
                  -     *      {
                  -     *          {4L, 6L},
                  -     *          {5L, 7L}
                  -     *      },
                  -     *      {
                  -     *          {1L, 3L},
                  -     *          {0L, 2L}
                  -     *      }
                  -     *  }
                  +     * client.mset(Map.of("key1", "abcd123", "key2", "bcdef123")).get();
                  +     * Map response = client.lcsIdx("key1", "key2").get();
                  +     * // the response contains data in the following format:
                  +     * Map data = Map.of(
                  +     *     "matches", new Long[][][] {
                  +     *         {                         // the first substring match is `"123"`
                  +     *             {4L, 6L},             // in `"key1"` it is located between indices `4` and `6`
                  +     *             {5L, 7L}              // and in `"key2"` - in between `5` and `7`
                  +     *         },
                  +     *         {                         // second substring match is `"bcd"`
                  +     *             {1L, 3L},             // in `"key1"` it is located between indices `1` and `3`
                  +     *             {0L, 2L}              // and in `"key2"` - in between `0` and `2`
                  +     *         }
                  +     *     },
                  +     *     "len", 6                      // total length of the all matches found
                  +     * );
                        * }
                  - * The result indicates that the first substring match is "123" in key1 - * at index 4 to 6 which matches the substring in key2 - * at index 5 to 7. And the second substring match is - * "bcd" in key1 at index 1 to 3 which matches - * the substring in key2 at index 0 to 2. */ CompletableFuture> lcsIdx(String key1, String key2); /** - * Returns the indices and length of the longest common subsequence between strings stored at - * key1 and key2. + * Returns the indices and the total length of all the longest common subsequences between strings + * stored at key1 and key2. * * @since Valkey 7.0 and above. * @apiNote When in cluster mode, key1 and key2 must map to the same @@ -809,41 +809,41 @@ public interface StringBaseCommands { * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the total length of all the longest common subsequences. The resulting map + * contains two keys, "matches" and "len": *
                    - *
                  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. + *
                  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. *
                  • "matches" is mapped to a three dimensional Long array that stores pairs * of indices that represent the location of the common subsequences in the strings held * by key1 and key2. *
                  - * - * @example If key1 holds the GlideString gs("abcd123") and key2 - * holds the GlideString gs("bcdef123") then the sample result would be + * See example for more details. + * @example *
                  {@code
                  -     * new Long[][][] {
                  -     *      {
                  -     *          {4L, 6L},
                  -     *          {5L, 7L}
                  -     *      },
                  -     *      {
                  -     *          {1L, 3L},
                  -     *          {0L, 2L}
                  -     *      }
                  -     *  }
                  +     * client.mset(Map.of(gs("key1"), gs("abcd123"), gs("key2"), gs("bcdef123"))).get();
                  +     * Map response = client.lcsIdx(gs("key1"), gs("key2")).get();
                  +     * // the response contains data in the following format:
                  +     * Map data = Map.of(
                  +     *     "matches", new Long[][][] {
                  +     *         {                         // the first substring match is `gs("123")`
                  +     *             {4L, 6L},             // in `gs("key1")` it is located between indices `4` and `6`
                  +     *             {5L, 7L}              // and in `gs("key2")` - in between `5` and `7`
                  +     *         },
                  +     *         {                         // second substring match is `gs("bcd")`
                  +     *             {1L, 3L},             // in `gs("key1")` it is located between indices `1` and `3`
                  +     *             {0L, 2L}              // and in `gs("key2")` - in between `0` and `2`
                  +     *         }
                  +     *     },
                  +     *     "len", 6                      // total length of the all matches found
                  +     * );
                        * }
                  - * The result indicates that the first substring match is gs("123") in key1 - * at index 4 to 6 which matches the substring in key2 - * at index 5 to 7. And the second substring match is - * gs("bcd") in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2. */ CompletableFuture> lcsIdx(GlideString key1, GlideString key2); /** - * Returns the indices and length of the longest common subsequence between strings stored at - * key1 and key2. + * Returns the indices and the total length of all the longest common subsequences between strings + * stored at key1 and key2. * * @since Valkey 7.0 and above. * @apiNote When in cluster mode, key1 and key2 must map to the same @@ -853,41 +853,42 @@ public interface StringBaseCommands { * @param key2 The key that stores the second string. * @param minMatchLen The minimum length of matches to include in the result. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the total length of all the longest common subsequences. The resulting map + * contains two keys, "matches" and "len": *
                    - *
                  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. + *
                  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. This value doesn't count towards the + * minMatchLen filter. *
                  • "matches" is mapped to a three dimensional Long array that stores pairs * of indices that represent the location of the common subsequences in the strings held * by key1 and key2. *
                  - * - * @example If key1 holds the string "abcd123" and key2 - * holds the string "bcdef123" then the sample result would be + * See example for more details. + * @example *
                  {@code
                  -     * new Long[][][] {
                  -     *      {
                  -     *          {4L, 6L},
                  -     *          {5L, 7L}
                  -     *      },
                  -     *      {
                  -     *          {1L, 3L},
                  -     *          {0L, 2L}
                  -     *      }
                  -     *  }
                  +     * client.mset(Map.of("key1", "abcd123", "key2", "bcdef123")).get();
                  +     * Map response = client.lcsIdx("key1", "key2", 2).get();
                  +     * // the response contains data in the following format:
                  +     * Map data = Map.of(
                  +     *     "matches", new Long[][][] {
                  +     *         {                         // the first substring match is `"123"`
                  +     *             {4L, 6L},             // in `"key1"` it is located between indices `4` and `6`
                  +     *             {5L, 7L}              // and in `"key2"` - in between `5` and `7`
                  +     *         },
                  +     *         {                         // second substring match is `"bcd"`
                  +     *             {1L, 3L},             // in `"key1"` it is located between indices `1` and `3`
                  +     *             {0L, 2L}              // and in `"key2"` - in between `0` and `2`
                  +     *         }
                  +     *     },
                  +     *     "len", 6                      // total length of the all matches found
                  +     * );
                        * }
                  - * The result indicates that the first substring match is "123" in key1 - * at index 4 to 6 which matches the substring in key2 - * at index 5 to 7. And the second substring match is - * "bcd" in key1 at index 1 to 3 which matches - * the substring in key2 at index 0 to 2. */ CompletableFuture> lcsIdx(String key1, String key2, long minMatchLen); /** - * Returns the indices and length of the longest common subsequence between strings stored at - * key1 and key2. + * Returns the indices and the total length of all the longest common subsequences between strings + * stored at key1 and key2. * * @since Valkey 7.0 and above. * @apiNote When in cluster mode, key1 and key2 must map to the same @@ -897,41 +898,42 @@ public interface StringBaseCommands { * @param key2 The key that stores the second string. * @param minMatchLen The minimum length of matches to include in the result. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the total length of all the longest common subsequences. The resulting map + * contains two keys, "matches" and "len": *
                    - *
                  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. + *
                  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. This value doesn't count towards the + * minMatchLen filter. *
                  • "matches" is mapped to a three dimensional Long array that stores pairs * of indices that represent the location of the common subsequences in the strings held * by key1 and key2. *
                  - * - * @example If key1 holds the GlideString gs("abcd123") and key2 - * holds the GlideString gs("bcdef123") then the sample result would be + * See example for more details. + * @example *
                  {@code
                  -     * new Long[][][] {
                  -     *      {
                  -     *          {4L, 6L},
                  -     *          {5L, 7L}
                  -     *      },
                  -     *      {
                  -     *          {1L, 3L},
                  -     *          {0L, 2L}
                  -     *      }
                  -     *  }
                  +     * client.mset(Map.of(gs("key1"), gs("abcd123"), gs("key2"), gs("bcdef123"))).get();
                  +     * Map response = client.lcsIdx(gs("key1"), gs("key2"), 2).get();
                  +     * // the response contains data in the following format:
                  +     * Map data = Map.of(
                  +     *     "matches", new Long[][][] {
                  +     *         {                         // the first substring match is `gs("123")`
                  +     *             {4L, 6L},             // in `gs("key1")` it is located between indices `4` and `6`
                  +     *             {5L, 7L}              // and in `gs("key2")` - in between `5` and `7`
                  +     *         },
                  +     *         {                         // second substring match is `gs("bcd")`
                  +     *             {1L, 3L},             // in `gs("key1")` it is located between indices `1` and `3`
                  +     *             {0L, 2L}              // and in `gs("key2")` - in between `0` and `2`
                  +     *         }
                  +     *     },
                  +     *     "len", 6                      // total length of the all matches found
                  +     * );
                        * }
                  - * The result indicates that the first substring match is gs("123") in key1 - * at index 4 to 6 which matches the substring in key2 - * at index 5 to 7. And the second substring match is - * gs("bcd") in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2. */ CompletableFuture> lcsIdx( GlideString key1, GlideString key2, long minMatchLen); /** - * Returns the indices and length of the longest common subsequence between strings stored at + * Returns the indices and lengths of the longest common subsequences between strings stored at * key1 and key2. * * @since Valkey 7.0 and above. @@ -941,42 +943,42 @@ CompletableFuture> lcsIdx( * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the lengths of the longest common subsequences. The resulting map contains + * two keys, "matches" and "len": *
                    - *
                  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. - *
                  • "matches" is mapped to a three dimensional Long array that stores pairs - * of indices that represent the location of the common subsequences in the strings held - * by key1 and key2. + *
                  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. + *
                  • "matches" is mapped to a three dimensional array that stores pairs of indices that + * represent the location of the common subsequences in the strings held by key1 + * and key2 and the match length. *
                  - * - * @example If key1 holds the string "abcd1234" and key2 - * holds the string "bcdef1234" then the sample result would be + * See example for more details. + * @example *
                  {@code
                  -     * new Object[] {
                  -     *      new Object[] {
                  -     *          new Long[] {4L, 7L},
                  -     *          new Long[] {5L, 8L},
                  -     *          4L},
                  -     *      new Object[] {
                  -     *          new Long[] {1L, 3L},
                  -     *          new Long[] {0L, 2L},
                  -     *          3L}
                  -     *      }
                  +     * client.mset(Map.of("key1", "abcd1234", "key2", "bcdef1234")).get();
                  +     * Map response = client.lcsIdxWithMatchLen("key1", "key2").get();
                  +     * // the response contains data in the following format:
                  +     * Map data = Map.of(
                  +     *     "matches", new Object[][] {
                  +     *         {                                    // the first substring match is `"1234"`
                  +     *             new Long[] {4L, 7L},             // in `"key1"` it is located between indices `4` and `7`
                  +     *             new Long[] {5L, 8L},             // and in `"key2"` - in between `5` and `8`
                  +     *             4L                               // the match length
                  +     *         },
                  +     *         {                                    // second substring match is `"bcd"`
                  +     *             new Long[] {1L, 3L},             // in `"key1"` it is located between indices `1` and `3`
                  +     *             new Long[] {0L, 2L},             // and in `"key2"` - in between `0` and `2`
                  +     *             3L                               // the match length
                  +     *         }
                  +     *     },
                  +     *     "len", 6                                 // total length of the all matches found
                  +     * );
                        * }
                  - * The result indicates that the first substring match is "1234" in key1 - * at index 4 to 7 which matches the substring in key2 - * at index 5 to 8 and the last element in the array is the - * length of the substring match which is 4. And the second substring match is - * "bcd" in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2 and - * the last element in the array is the length of the substring match which is 3. */ CompletableFuture> lcsIdxWithMatchLen(String key1, String key2); /** - * Returns the indices and length of the longest common subsequence between strings stored at + * Returns the indices and lengths of the longest common subsequences between strings stored at * key1 and key2. * * @since Valkey 7.0 and above. @@ -986,43 +988,42 @@ CompletableFuture> lcsIdx( * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the lengths of the longest common subsequences. The resulting map contains + * two keys, "matches" and "len": *
                    - *
                  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. - *
                  • "matches" is mapped to a three dimensional Long array that stores pairs - * of indices that represent the location of the common subsequences in the strings held - * by key1 and key2. + *
                  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. + *
                  • "matches" is mapped to a three dimensional array that stores pairs of indices that + * represent the location of the common subsequences in the strings held by key1 + * and key2 and the match length. *
                  - * - * @example If key1 holds the GlideString gs("abcd1234") and key2 - * holds the GlideString gs("bcdef1234") then the sample result would be + * See example for more details. + * @example *
                  {@code
                  -     * new Object[] {
                  -     *      new Object[] {
                  -     *          new Long[] {4L, 7L},
                  -     *          new Long[] {5L, 8L},
                  -     *          4L},
                  -     *      new Object[] {
                  -     *          new Long[] {1L, 3L},
                  -     *          new Long[] {0L, 2L},
                  -     *          3L}
                  -     *      }
                  +     * client.mset(Map.of(gs("key1"), gs("abcd1234"), gs("key2"), gs("bcdef1234"))).get();
                  +     * Map response = client.lcsIdxWithMatchLen(gs("key1"), gs("key2")).get();
                  +     * // the response contains data in the following format:
                  +     * Map data = Map.of(
                  +     *     "matches", new Object[][] {
                  +     *         {                                    // the first substring match is `gs("1234")`
                  +     *             new Long[] {4L, 7L},             // in `gs("key1")` it is located between indices `4` and `7`
                  +     *             new Long[] {5L, 8L},             // and in `gs("key2")` - in between `5` and `8`
                  +     *             4L                               // the match length
                  +     *         },
                  +     *         {                                    // second substring match is `"bcd"`
                  +     *             new Long[] {1L, 3L},             // in `gs("key1")` it is located between indices `1` and `3`
                  +     *             new Long[] {0L, 2L},             // and in `gs("key2")` - in between `0` and `2`
                  +     *             3L                               // the match length
                  +     *         }
                  +     *     },
                  +     *     "len", 6                                 // total length of the all matches found
                  +     * );
                        * }
                  - * The result indicates that the first substring match is gs("1234") in - * key1 - * at index 4 to 7 which matches the substring in key2 - * at index 5 to 8 and the last element in the array is the - * length of the substring match which is 4. And the second substring match is - * gs("bcd") in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2 and - * the last element in the array is the length of the substring match which is 3. */ CompletableFuture> lcsIdxWithMatchLen(GlideString key1, GlideString key2); /** - * Returns the indices and length of the longest common subsequence between strings stored at + * Returns the indices and lengths of the longest common subsequences between strings stored at * key1 and key2. * * @since Valkey 7.0 and above. @@ -1033,43 +1034,44 @@ CompletableFuture> lcsIdx( * @param key2 The key that stores the second string. * @param minMatchLen The minimum length of matches to include in the result. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the total length of all the longest common subsequences. The resulting map + * contains two keys, "matches" and "len": *
                    - *
                  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. - *
                  • "matches" is mapped to a three dimensional Long array that stores pairs - * of indices that represent the location of the common subsequences in the strings held - * by key1 and key2. + *
                  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. This value doesn't count towards the + * minMatchLen filter. + *
                  • "matches" is mapped to a three dimensional array that stores pairs of indices that + * represent the location of the common subsequences in the strings held by key1 + * and key2 and the match length. *
                  - * - * @example If key1 holds the string "abcd1234" and key2 - * holds the string "bcdef1234" then the sample result would be + * See example for more details. + * @example *
                  {@code
                  -     * new Object[] {
                  -     *      new Object[] {
                  -     *          new Long[] {4L, 7L},
                  -     *          new Long[] {5L, 8L},
                  -     *          4L},
                  -     *      new Object[] {
                  -     *          new Long[] {1L, 3L},
                  -     *          new Long[] {0L, 2L},
                  -     *          3L}
                  -     *      }
                  +     * client.mset(Map.of("key1", "abcd1234", "key2", "bcdef1234")).get();
                  +     * Map response = client.lcsIdxWithMatchLen("key1", "key2", 2).get();
                  +     * // the response contains data in the following format:
                  +     * Map data = Map.of(
                  +     *     "matches", new Object[][] {
                  +     *         {                                    // the first substring match is `"1234"`
                  +     *             new Long[] {4L, 7L},             // in `"key1"` it is located between indices `4` and `7`
                  +     *             new Long[] {5L, 8L},             // and in `"key2"` - in between `5` and `8`
                  +     *             4L                               // the match length
                  +     *         },
                  +     *         {                                    // second substring match is `"bcd"`
                  +     *             new Long[] {1L, 3L},             // in `"key1"` it is located between indices `1` and `3`
                  +     *             new Long[] {0L, 2L},             // and in `"key2"` - in between `0` and `2`
                  +     *             3L                               // the match length
                  +     *         }
                  +     *     },
                  +     *     "len", 6                                 // total length of the all matches found
                  +     * );
                        * }
                  - * The result indicates that the first substring match is "1234" in key1 - * at index 4 to 7 which matches the substring in key2 - * at index 5 to 8 and the last element in the array is the - * length of the substring match which is 4. And the second substring match is - * "bcd" in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2 and - * the last element in the array is the length of the substring match which is 3. */ CompletableFuture> lcsIdxWithMatchLen( String key1, String key2, long minMatchLen); /** - * Returns the indices and length of the longest common subsequence between strings stored at + * Returns the indices and lengths of the longest common subsequences between strings stored at * key1 and key2. * * @since Valkey 7.0 and above. @@ -1080,38 +1082,38 @@ CompletableFuture> lcsIdxWithMatchLen( * @param key2 The key that stores the second string. * @param minMatchLen The minimum length of matches to include in the result. * @return A Map containing the indices of the longest common subsequence between the - * 2 strings and the length of the longest common subsequence. The resulting map contains two - * keys, "matches" and "len": + * 2 strings and the total length of all the longest common subsequences. The resulting map + * contains two keys, "matches" and "len": *
                    - *
                  • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. - *
                  • "matches" is mapped to a three dimensional Long array that stores pairs - * of indices that represent the location of the common subsequences in the strings held - * by key1 and key2. + *
                  • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. This value doesn't count towards the + * minMatchLen filter. + *
                  • "matches" is mapped to a three dimensional array that stores pairs of indices that + * represent the location of the common subsequences in the strings held by key1 + * and key2 and the match length. *
                  - * - * @example If key1 holds the GlideString gs("abcd1234") and key2 - * holds the GlideString gs("bcdef1234") then the sample result would be + * See example for more details. + * @example *
                  {@code
                  -     * new Object[] {
                  -     *      new Object[] {
                  -     *          new Long[] {4L, 7L},
                  -     *          new Long[] {5L, 8L},
                  -     *          4L},
                  -     *      new Object[] {
                  -     *          new Long[] {1L, 3L},
                  -     *          new Long[] {0L, 2L},
                  -     *          3L}
                  -     *      }
                  +     * client.mset(Map.of(gs("key1"), gs("abcd1234"), gs("key2"), gs("bcdef1234"))).get();
                  +     * Map response = client.lcsIdxWithMatchLen(gs("key1"), gs("key2"), 2).get();
                  +     * // the response contains data in the following format:
                  +     * Map data = Map.of(
                  +     *     "matches", new Object[][] {
                  +     *         {                                    // the first substring match is `gs("1234")`
                  +     *             new Long[] {4L, 7L},             // in `gs("key1")` it is located between indices `4` and `7`
                  +     *             new Long[] {5L, 8L},             // and in `gs("key2")` - in between `5` and `8`
                  +     *             4L                               // the match length
                  +     *         },
                  +     *         {                                    // second substring match is `"bcd"`
                  +     *             new Long[] {1L, 3L},             // in `gs("key1")` it is located between indices `1` and `3`
                  +     *             new Long[] {0L, 2L},             // and in `gs("key2")` - in between `0` and `2`
                  +     *             3L                               // the match length
                  +     *         }
                  +     *     },
                  +     *     "len", 6                                 // total length of the all matches found
                  +     * );
                        * }
                  - * The result indicates that the first substring match is gs("1234") in - * key1 - * at index 4 to 7 which matches the substring in key2 - * at index 5 to 8 and the last element in the array is the - * length of the substring match which is 4. And the second substring match is - * gs("bcd") in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2 and - * the last element in the array is the length of the substring match which is 3. */ CompletableFuture> lcsIdxWithMatchLen( GlideString key1, GlideString key2, long minMatchLen); diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index ec2ab02b0c..94a2ab59a8 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -105,6 +105,9 @@ import static command_request.CommandRequestOuterClass.RequestType.PfCount; import static command_request.CommandRequestOuterClass.RequestType.PfMerge; import static command_request.CommandRequestOuterClass.RequestType.Ping; +import static command_request.CommandRequestOuterClass.RequestType.PubSubChannels; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumPat; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumSub; import static command_request.CommandRequestOuterClass.RequestType.Publish; import static command_request.CommandRequestOuterClass.RequestType.RPop; import static command_request.CommandRequestOuterClass.RequestType.RPush; @@ -215,12 +218,14 @@ import static glide.utils.ArrayTransformUtils.flattenAllKeysFollowedByAllValues; import static glide.utils.ArrayTransformUtils.flattenMapToGlideStringArray; import static glide.utils.ArrayTransformUtils.flattenMapToGlideStringArrayValueFirst; +import static glide.utils.ArrayTransformUtils.flattenNestedArrayToGlideStringArray; import static glide.utils.ArrayTransformUtils.mapGeoDataToGlideStringArray; import command_request.CommandRequestOuterClass.Command; import command_request.CommandRequestOuterClass.Command.ArgsArray; import command_request.CommandRequestOuterClass.RequestType; import command_request.CommandRequestOuterClass.Transaction; +import glide.api.commands.StringBaseCommands; import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.FlushMode; import glide.api.models.commands.GetExOptions; @@ -338,7 +343,7 @@ public T withBinaryOutput() { * href="https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command">Glide * Wiki for details on the restrictions and limitations of the custom command API. * @param args Arguments for the custom command. - * @return Command Response - A response from the server with an Object. + * @return Command Response - The returned value for the custom command. */ public T customCommand(ArgType[] args) { checkTypeOrThrow(args); @@ -3034,7 +3039,7 @@ public T zintercard(@NonNull ArgType[] keys, long limit) { * , and stores the result in destination. If destination already * exists, it is overwritten. Otherwise, a new sorted set will be created.
                  * To perform a zinterstore operation while specifying aggregation settings, use - * {@link #zinterstore(Object, KeysOrWeightedKeys, Aggregate)}. + * {@link #zinterstore(String, KeysOrWeightedKeys, Aggregate)}. * * @see valkey.io for more details. * @param destination The key of the destination sorted set. @@ -3060,7 +3065,7 @@ public T zinterstore( * , and stores the result in destination. If destination already * exists, it is overwritten. Otherwise, a new sorted set will be created.
                  * To perform a zinterstore operation while specifying aggregation settings, use - * {@link #zinterstore(Object, KeysOrWeightedKeys, Aggregate)}. + * {@link #zinterstore(GlideString, KeysOrWeightedKeysBinary, Aggregate)}. * * @see valkey.io for more details. * @param destination The key of the destination sorted set. @@ -3343,7 +3348,8 @@ public T zinterWithScores( /** * Adds an entry to the specified stream stored at key.
                  - * If the key doesn't exist, the stream is created. + * If the key doesn't exist, the stream is created. To add entries with duplicate + * keys, use {@link #xadd(ArgType, ArgType[][])}. * * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. @@ -3358,7 +3364,24 @@ public T xadd(@NonNull ArgType key, @NonNull Map val /** * Adds an entry to the specified stream stored at key.
                  - * If the key doesn't exist, the stream is created. + * If the key doesn't exist, the stream is created. This method overload allows + * entries with duplicate keys to be added. + * + * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type + * will throw {@link IllegalArgumentException}. + * @see valkey.io for details. + * @param key The key of the stream. + * @param values Field-value pairs to be added to the entry. + * @return Command Response - The id of the added entry. + */ + public T xadd(@NonNull ArgType key, @NonNull ArgType[][] values) { + return xadd(key, values, StreamAddOptions.builder().build()); + } + + /** + * Adds an entry to the specified stream stored at key.
                  + * If the key doesn't exist, the stream is created. To add entries with duplicate + * keys, use {@link #xadd(ArgType, ArgType[][], StreamAddOptions)}. * * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. @@ -3385,19 +3408,44 @@ public T xadd( return getThis(); } + /** + * Adds an entry to the specified stream stored at key.
                  + * If the key doesn't exist, the stream is created. This method overload allows + * entries with duplicate keys to be added. + * + * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type + * will throw {@link IllegalArgumentException}. + * @see valkey.io for details. + * @param key The key of the stream. + * @param values Field-value pairs to be added to the entry. + * @param options Stream add options {@link StreamAddOptions}. + * @return Command Response - The id of the added entry, or null if {@link + * StreamAddOptionsBuilder#makeStream(Boolean)} is set to false and no stream + * with the matching key exists. + */ + public T xadd( + @NonNull ArgType key, @NonNull ArgType[][] values, @NonNull StreamAddOptions options) { + checkTypeOrThrow(key); + protobufTransaction.addCommands( + buildCommand( + XAdd, + newArgsBuilder() + .add(key) + .add(options.toArgs()) + .add(flattenNestedArrayToGlideStringArray(values)))); + return getThis(); + } + /** * Reads entries from the given streams. * * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. * @see valkey.io for details. - * @param keysAndIds An array of Pairs of keys and entry ids to read from. A - * pair is composed of a stream's key and the id of the entry after which the stream - * will be read. + * @param keysAndIds A Map of keys and entry IDs to read from. * @return Command Response - A {@literal Map>} with stream keys, to Map of stream-ids, to an array of - * pairings with format [[field, entry], - * [field, entry], ...]. + * String[][]>>}
                  with stream keys, to Map of stream entry IDs, to an array + * of pairings with format [[field, entry], [field, entry], ...]. */ public T xread(@NonNull Map keysAndIds) { return xread(keysAndIds, StreamReadOptions.builder().build()); @@ -3409,14 +3457,11 @@ public T xread(@NonNull Map keysAndIds) { * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. * @see valkey.io for details. - * @param keysAndIds An array of Pairs of keys and entry ids to read from. A - * pair is composed of a stream's key and the id of the entry after which the stream - * will be read. + * @param keysAndIds A Map of keys and entry IDs to read from. * @param options options detailing how to read the stream {@link StreamReadOptions}. * @return Command Response - A {@literal Map>} with stream keys, to Map of stream-ids, to an array of - * pairings with format [[field, entry], - * [field, entry], ...]. + * String[][]>>}
                  with stream keys, to Map of stream entry IDs, to an array + * of pairings with format [[field, entry], [field, entry], ...]. */ public T xread( @NonNull Map keysAndIds, @NonNull StreamReadOptions options) { @@ -3488,25 +3533,25 @@ public T xdel(@NonNull ArgType key, @NonNull ArgType[] ids) { * will throw {@link IllegalArgumentException}. * @see valkey.io for details. * @param key The key of the stream. - * @param start Starting stream ID bound for range. + * @param start Starting stream entry IDs bound for range. *
                    - *
                  • Use {@link StreamRange.IdBound#of} to specify a stream ID. + *
                  • Use {@link StreamRange.IdBound#of} to specify a stream entry IDs. *
                  • Use {@link StreamRange.IdBound#ofExclusive} to specify an exclusive bounded stream * ID. *
                  • Use {@link StreamRange.InfRangeBound#MIN} to start with the minimum available ID. *
                  * - * @param end Ending stream ID bound for range. + * @param end Ending stream entry IDs bound for range. *
                    - *
                  • Use {@link StreamRange.IdBound#of} to specify a stream ID. + *
                  • Use {@link StreamRange.IdBound#of} to specify a stream entry IDs. *
                  • Use {@link StreamRange.IdBound#ofExclusive} to specify an exclusive bounded stream * ID. *
                  • Use {@link StreamRange.InfRangeBound#MAX} to end with the maximum available ID. *
                  * * @return Command Response - A Map of key to stream entry data, where entry data is - * an array of pairings with format [[field, - * entry], [field, entry], ...]. + * an array of pairings with format [[field, entry], [field, entry], ...]. + * Returns or null if count is non-positive. */ public T xrange( @NonNull ArgType key, @NonNull StreamRange start, @NonNull StreamRange end) { @@ -3523,17 +3568,17 @@ public T xrange( * will throw {@link IllegalArgumentException}. * @see valkey.io for details. * @param key The key of the stream. - * @param start Starting stream ID bound for range. + * @param start Starting stream entry IDs bound for range. *
                    - *
                  • Use {@link StreamRange.IdBound#of} to specify a stream ID. + *
                  • Use {@link StreamRange.IdBound#of} to specify a stream entry IDs. *
                  • Use {@link StreamRange.IdBound#ofExclusive} to specify an exclusive bounded stream * ID. *
                  • Use {@link StreamRange.InfRangeBound#MIN} to start with the minimum available ID. *
                  * - * @param end Ending stream ID bound for range. + * @param end Ending stream entry IDs bound for range. *
                    - *
                  • Use {@link StreamRange.IdBound#of} to specify a stream ID. + *
                  • Use {@link StreamRange.IdBound#of} to specify a stream entry IDs. *
                  • Use {@link StreamRange.IdBound#ofExclusive} to specify an exclusive bounded stream * ID. *
                  • Use {@link StreamRange.InfRangeBound#MAX} to end with the maximum available ID. @@ -3541,8 +3586,8 @@ public T xrange( * * @param count Maximum count of stream entries to return. * @return Command Response - A Map of key to stream entry data, where entry data is - * an array of pairings with format [[field, - * entry], [field, entry], ...]. + * an array of pairings with format [[field, entry], [field, entry], ...]. + * Returns or null if count is non-positive. */ public T xrange( @NonNull ArgType key, @NonNull StreamRange start, @NonNull StreamRange end, long count) { @@ -3561,25 +3606,25 @@ public T xrange( * will throw {@link IllegalArgumentException}. * @see valkey.io for details. * @param key The key of the stream. - * @param end Ending stream ID bound for range. + * @param end Ending stream entry IDs bound for range. *
                      - *
                    • Use {@link StreamRange.IdBound#of} to specify a stream ID. + *
                    • Use {@link StreamRange.IdBound#of} to specify a stream entry IDs. *
                    • Use {@link StreamRange.IdBound#ofExclusive} to specify an exclusive bounded stream * ID. *
                    • Use {@link StreamRange.InfRangeBound#MAX} to end with the maximum available ID. *
                    * - * @param start Starting stream ID bound for range. + * @param start Starting stream entry IDs bound for range. *
                      - *
                    • Use {@link StreamRange.IdBound#of} to specify a stream ID. + *
                    • Use {@link StreamRange.IdBound#of} to specify a stream entry IDs. *
                    • Use {@link StreamRange.IdBound#ofExclusive} to specify an exclusive bounded stream * ID. *
                    • Use {@link StreamRange.InfRangeBound#MIN} to start with the minimum available ID. *
                    * * @return Command Response - A Map of key to stream entry data, where entry data is - * an array of pairings with format [[field, - * entry], [field, entry], ...]. + * an array of pairings with format [[field, entry], [field, entry], ...]. + * Returns or null if count is non-positive. */ public T xrevrange( @NonNull ArgType key, @NonNull StreamRange end, @NonNull StreamRange start) { @@ -3598,17 +3643,17 @@ public T xrevrange( * will throw {@link IllegalArgumentException}. * @see valkey.io for details. * @param key The key of the stream. - * @param start Starting stream ID bound for range. + * @param start Starting stream entry IDs bound for range. *
                      - *
                    • Use {@link StreamRange.IdBound#of} to specify a stream ID. + *
                    • Use {@link StreamRange.IdBound#of} to specify a stream entry IDs. *
                    • Use {@link StreamRange.IdBound#ofExclusive} to specify an exclusive bounded stream * ID. *
                    • Use {@link StreamRange.InfRangeBound#MIN} to start with the minimum available ID. *
                    * - * @param end Ending stream ID bound for range. + * @param end Ending stream entry IDs bound for range. *
                      - *
                    • Use {@link StreamRange.IdBound#of} to specify a stream ID. + *
                    • Use {@link StreamRange.IdBound#of} to specify a stream entry IDs. *
                    • Use {@link StreamRange.IdBound#ofExclusive} to specify an exclusive bounded stream * ID. *
                    • Use {@link StreamRange.InfRangeBound#MAX} to end with the maximum available ID. @@ -3617,6 +3662,7 @@ public T xrevrange( * @param count Maximum count of stream entries to return. * @return Command Response - A Map of key to stream entry data, where entry data is * an array of pairings with format [[field, entry], [field, entry], ...]. + * Returns or null if count is non-positive. */ public T xrevrange( @NonNull ArgType key, @NonNull StreamRange end, @NonNull StreamRange start, long count) { @@ -3790,17 +3836,15 @@ public T xgroupSetId( * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. * @see valkey.io for details. - * @param keysAndIds A Map of keys and entry ids to read from. The Map - * is composed of a stream's key and the id of the entry after which the stream will be read. - * Use the special id of {@literal Map>} - * to receive only new messages. + * @param keysAndIds A Map of keys and entry IDs to read from.
                      + * Use the special ID of {@literal ">"} to receive only new messages. * @param group The consumer group name. * @param consumer The newly created consumer. * @return Command Response - A {@literal Map>} with - * stream keys, to Map of stream-ids, to an array of pairings with format - * [[field, entry], [field, entry], ...]. Returns null if the consumer - * group does not exist. Returns a Map with a value of code>null if the - * stream is empty. + * stream keys, to Map of stream entry IDs, to an array of pairings with format + * + * [[field, entry], [field, entry], ...]. Returns null if there is no + * stream that can be served. */ public T xreadgroup( @NonNull Map keysAndIds, @@ -3816,18 +3860,16 @@ public T xreadgroup( * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. * @see valkey.io for details. - * @param keysAndIds A Map of keys and entry ids to read from. The Map - * is composed of a stream's key and the id of the entry after which the stream will be read. - * Use the special id of {@literal Map>} to - * receive only new messages. + * @param keysAndIds A Map of keys and entry IDs to read from.
                      + * Use the special ID of {@literal ">"} to receive only new messages. * @param group The consumer group name. * @param consumer The newly created consumer. * @param options Options detailing how to read the stream {@link StreamReadGroupOptions}. * @return Command Response - A {@literal Map>} with - * stream keys, to Map of stream-ids, to an array of pairings with format - * [[field, entry], [field, entry], ...]. Returns null if the consumer - * group does not exist. Returns a Map with a value of code>null if the - * stream is empty. + * stream keys, to Map of stream entry IDs, to an array of pairings with format + * + * [[field, entry], [field, entry], ...]. Returns null if there is no + * stream that can be served. */ public T xreadgroup( @NonNull Map keysAndIds, @@ -3899,17 +3941,17 @@ public T xpending(@NonNull ArgType key, @NonNull ArgType group) { * @see valkey.io for details. * @param key The key of the stream. * @param group The consumer group name. - * @param start Starting stream ID bound for range. + * @param start Starting stream entry IDs bound for range. *
                        - *
                      • Use {@link IdBound#of} to specify a stream ID. - *
                      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
                      • Use {@link IdBound#of} to specify a stream entry IDs. + *
                      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry IDs. *
                      • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
                      * - * @param end Ending stream ID bound for range. + * @param end Ending stream entry IDs bound for range. *
                        - *
                      • Use {@link IdBound#of} to specify a stream ID. - *
                      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
                      • Use {@link IdBound#of} to specify a stream entry IDs. + *
                      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry IDs. *
                      • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
                      * @@ -3945,17 +3987,17 @@ public T xpending( * @see valkey.io for details. * @param key The key of the stream. * @param group The consumer group name. - * @param start Starting stream ID bound for range. + * @param start Starting stream entry IDs bound for range. *
                        - *
                      • Use {@link IdBound#of} to specify a stream ID. - *
                      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
                      • Use {@link IdBound#of} to specify a stream entry IDs. + *
                      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry IDs. *
                      • Use {@link InfRangeBound#MIN} to start with the minimum available ID. *
                      * - * @param end Ending stream ID bound for range. + * @param end Ending stream entry IDs bound for range. *
                        - *
                      • Use {@link IdBound#of} to specify a stream ID. - *
                      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream ID. + *
                      • Use {@link IdBound#of} to specify a stream entry IDs. + *
                      • Use {@link IdBound#ofExclusive} to specify an exclusive bounded stream entry IDs. *
                      • Use {@link InfRangeBound#MAX} to end with the maximum available ID. *
                      * @@ -4219,7 +4261,8 @@ public T xinfoConsumers(@NonNull ArgType key, @NonNull ArgType groupNa * specified value. * @return Command Response - An array containing the following elements: *
                        - *
                      • A stream ID to be used as the start argument for the next call to XAUTOCLAIM + *
                      • A stream entry IDs to be used as the start argument for the next call to + * XAUTOCLAIM * . This ID is equivalent to the next ID in the stream after the entries that * were scanned, or "0-0" if the entire stream was scanned. *
                      • A mapping of the claimed entries, with the keys being the claimed entry IDs and the @@ -4258,7 +4301,8 @@ public T xautoclaim( * @param count Limits the number of claimed entries to the specified value. * @return Command Response - An array containing the following elements: *
                          - *
                        • A stream ID to be used as the start argument for the next call to XAUTOCLAIM + *
                        • A stream entry IDs to be used as the start argument for the next call to + * XAUTOCLAIM * . This ID is equivalent to the next ID in the stream after the entries that * were scanned, or "0-0" if the entire stream was scanned. *
                        • A mapping of the claimed entries, with the keys being the claimed entry IDs and the @@ -4306,7 +4350,8 @@ public T xautoclaim( * specified value. * @return Command Response - An array containing the following elements: *
                            - *
                          • A stream ID to be used as the start argument for the next call to XAUTOCLAIM + *
                          • A stream entry IDs to be used as the start argument for the next call to + * XAUTOCLAIM * . This ID is equivalent to the next ID in the stream after the entries that * were scanned, or "0-0" if the entire stream was scanned. *
                          • A list of the IDs for the claimed entries. @@ -4351,7 +4396,8 @@ public T xautoclaimJustId( * @param count Limits the number of claimed entries to the specified value. * @return Command Response - An array containing the following elements: *
                              - *
                            • A stream ID to be used as the start argument for the next call to XAUTOCLAIM + *
                            • A stream entry IDs to be used as the start argument for the next call to + * XAUTOCLAIM * . This ID is equivalent to the next ID in the stream after the entries that * were scanned, or "0-0" if the entire stream was scanned. *
                            • A list of the IDs for the claimed entries. @@ -5179,9 +5225,9 @@ public T copy(@NonNull ArgType source, @NonNull ArgType destination) { * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. * @see valkey.io for details. - * @param key The key of the set. - * @return Command Response - The serialized value of a set. If key does not exist, - * null will be returned. + * @param key The key to serialize. + * @return Command Response - The serialized value of the data stored at key.
                              + * If key does not exist, null will be returned. */ public T dump(@NonNull ArgType key) { checkTypeOrThrow(key); @@ -5196,12 +5242,12 @@ public T dump(@NonNull ArgType key) { * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. * @see valkey.io for details. - * @param key The key of the set. + * @param key The key to create. * @param ttl The expiry time (in milliseconds). If 0, the key will * persist. - * @param value The serialized value. - * @return Command Response - Return OK if successfully create a key - * with a value. + * @param value The serialized value to deserialize and assign to key. + * @return Command Response - Return OK if the key was successfully + * restored with a value. */ public T restore(@NonNull ArgType key, long ttl, @NonNull byte[] value) { checkTypeOrThrow(key); @@ -5216,14 +5262,15 @@ public T restore(@NonNull ArgType key, long ttl, @NonNull byte[] value * * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. + * @apiNote IDLETIME and FREQ modifiers cannot be set at the same time. * @see valkey.io for details. - * @param key The key of the set. + * @param key The key to create. * @param ttl The expiry time (in milliseconds). If 0, the key will * persist. - * @param value The serialized value. + * @param value The serialized value to deserialize and assign to key. * @param restoreOptions The restore options. See {@link RestoreOptions}. - * @return Command Response - Return OK if successfully create a key - * with a value. + * @return Command Response - Return OK if the key was successfully + * restored with a value. */ public T restore( @NonNull ArgType key, @@ -6193,8 +6240,8 @@ public T functionDelete(@NonNull ArgType libName) { } /** - * Returns the longest common subsequence between strings stored at key1 and - * key2. + * Returns all the longest common subsequences combined between strings stored at key1 + * and key2. * * @since Valkey 7.0 and above. * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type @@ -6202,9 +6249,9 @@ public T functionDelete(@NonNull ArgType libName) { * @see valkey.io for details. * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. - * @return Command Response - A String containing the longest common subsequence - * between the 2 strings. An empty String is returned if the keys do not exist or - * have no common subsequences. + * @return Command Response - A String containing all the longest common subsequences + * combined between the 2 strings. An empty String/GlideString is + * returned if the keys do not exist or have no common subsequences. */ public T lcs(@NonNull ArgType key1, @NonNull ArgType key2) { checkTypeOrThrow(key1); @@ -6213,8 +6260,8 @@ public T lcs(@NonNull ArgType key1, @NonNull ArgType key2) { } /** - * Returns the length of the longest common subsequence between strings stored at key1 - * and key2. + * Returns the total length of all the longest common subsequences between strings stored at + * key1 and key2. * * @since Valkey 7.0 and above. * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type @@ -6222,7 +6269,8 @@ public T lcs(@NonNull ArgType key1, @NonNull ArgType key2) { * @see valkey.io for details. * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. - * @return Command Response - The length of the longest common subsequence between the 2 strings. + * @return Command Response - The total length of all the longest common subsequences between the + * 2 strings. */ public T lcsLen(@NonNull ArgType key1, @NonNull ArgType key2) { checkTypeOrThrow(key1); @@ -6248,6 +6296,75 @@ public T publish(@NonNull ArgType message, @NonNull ArgType channel) { return getThis(); } + /** + * Lists the currently active channels. + * + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + * @see valkey.io for details. + * @return Command response - An Array of all active channels. + */ + public T pubsubChannels() { + protobufTransaction.addCommands(buildCommand(PubSubChannels)); + return getThis(); + } + + /** + * Lists the currently active channels. + * + * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type + * will throw {@link IllegalArgumentException}. + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + * @see valkey.io for details. + * @param pattern A glob-style pattern to match active channels. + * @return Command response - An Array of currently active channels matching the + * given pattern. + */ + public T pubsubChannels(@NonNull ArgType pattern) { + checkTypeOrThrow(pattern); + protobufTransaction.addCommands(buildCommand(PubSubChannels, newArgsBuilder().add(pattern))); + return getThis(); + } + + /** + * Returns the number of unique patterns that are subscribed to by clients. + * + * @apiNote + *
                                + *
                              • When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single array. + *
                              • This is the total number of unique patterns all the clients are subscribed to, not + * the count of clients subscribed to patterns. + *
                              + * + * @see valkey.io for details. + * @return Command response - The number of unique patterns. + */ + public T pubsubNumPat() { + protobufTransaction.addCommands(buildCommand(PubSubNumPat)); + return getThis(); + } + + /** + * Returns the number of subscribers (exclusive of clients subscribed to patterns) for the + * specified channels. + * + * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type + * will throw {@link IllegalArgumentException}. + * @apiNote When in cluster mode, the command is routed to all nodes, and aggregates the response + * into a single map. + * @see valkey.io for details. + * @param channels The list of channels to query for the number of subscribers. + * @return Command response - A Map where keys are the channel names and values are + * the numbers of subscribers. + */ + public T pubsubNumSub(@NonNull ArgType[] channels) { + checkTypeOrThrow(channels); + protobufTransaction.addCommands(buildCommand(PubSubNumSub, newArgsBuilder().add(channels))); + return getThis(); + } + /** * Gets the union of all the given sets. * @@ -6275,35 +6392,16 @@ public T sunion(@NonNull ArgType[] keys) { * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. * @return Command Response - A Map containing the indices of the longest common - * subsequence between the 2 strings and the length of the longest common subsequence. The - * resulting map contains two keys, "matches" and "len": + * subsequence between the 2 strings and the total length of all the longest common + * subsequences. The resulting map contains two keys, "matches" and "len": *
                                - *
                              • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. + *
                              • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. *
                              • "matches" is mapped to a three dimensional Long array that stores pairs * of indices that represent the location of the common subsequences in the strings held * by key1 and key2. *
                              - * - * @example If key1 holds the string "abcd123" and key2 - * holds the string "bcdef123" then the sample result would be - *
                              {@code
                              -     * new Long[][][] {
                              -     *     {
                              -     *         {4L, 6L},
                              -     *         {5L, 7L}
                              -     *     },
                              -     *     {
                              -     *         {1L, 3L},
                              -     *         {0L, 2L}
                              -     *     }
                              -     * }
                              -     * }
                              - * The result indicates that the first substring match is "123" in key1 - * at index 4 to 6 which matches the substring in key2 - * at index 5 to 7. And the second substring match is - * "bcd" in key1 at index 1 to 3 which matches - * the substring in key2 at index 0 to 2. + * See example of {@link StringBaseCommands#lcsIdx(String, String)} for more details. */ public T lcsIdx(@NonNull ArgType key1, @NonNull ArgType key2) { checkTypeOrThrow(key1); @@ -6313,8 +6411,8 @@ public T lcsIdx(@NonNull ArgType key1, @NonNull ArgType key2) { } /** - * Returns the indices and length of the longest common subsequence between strings stored at - * key1 and key2. + * Returns the indices and the total length of all the longest common subsequences between strings + * stored at key1 and key2. * * @since Valkey 7.0 and above. * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type @@ -6324,35 +6422,17 @@ public T lcsIdx(@NonNull ArgType key1, @NonNull ArgType key2) { * @param key2 The key that stores the second string. * @param minMatchLen The minimum length of matches to include in the result. * @return Command Response - A Map containing the indices of the longest common - * subsequence between the 2 strings and the length of the longest common subsequence. The - * resulting map contains two keys, "matches" and "len": + * subsequence between the 2 strings and the total length of all the longest common + * subsequences. The resulting map contains two keys, "matches" and "len": *
                                - *
                              • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. + *
                              • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. This value doesn't count towards the + * minMatchLen filter. *
                              • "matches" is mapped to a three dimensional Long array that stores pairs * of indices that represent the location of the common subsequences in the strings held * by key1 and key2. *
                              - * - * @example If key1 holds the string "abcd123" and key2 - * holds the string "bcdef123" then the sample result would be - *
                              {@code
                              -     * new Long[][][] {
                              -     *     {
                              -     *         {4L, 6L},
                              -     *         {5L, 7L}
                              -     *     },
                              -     *     {
                              -     *         {1L, 3L},
                              -     *         {0L, 2L}
                              -     *     }
                              -     * }
                              -     * }
                              - * The result indicates that the first substring match is "123" in key1 - * at index 4 to 6 which matches the substring in key2 - * at index 5 to 7. And the second substring match is - * "bcd" in key1 at index 1 to 3 which matches - * the substring in key2 at index 0 to 2. + * See example of {@link StringBaseCommands#lcsIdx(String, String, long)} for more details. */ public T lcsIdx(@NonNull ArgType key1, @NonNull ArgType key2, long minMatchLen) { checkTypeOrThrow(key1); @@ -6369,7 +6449,7 @@ public T lcsIdx(@NonNull ArgType key1, @NonNull ArgType key2, long min } /** - * Returns the indices and length of the longest common subsequence between strings stored at + * Returns the indices and lengths of the longest common subsequences between strings stored at * key1 and key2. * * @since Valkey 7.0 and above. @@ -6379,39 +6459,17 @@ public T lcsIdx(@NonNull ArgType key1, @NonNull ArgType key2, long min * @param key1 The key that stores the first string. * @param key2 The key that stores the second string. * @return Command Response - A Map containing the indices of the longest common - * subsequence between the 2 strings and the length of the longest common subsequence. The + * subsequence between the 2 strings and the lengths of the longest common subsequences. The * resulting map contains two keys, "matches" and "len": *
                                - *
                              • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. - *
                              • "matches" is mapped to a three dimensional Long array that stores pairs - * of indices that represent the location of the common subsequences in the strings held - * by key1 and key2. + *
                              • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. + *
                              • "matches" is mapped to a three dimensional array that stores pairs of indices that + * represent the location of the common subsequences in the strings held by key1 + * and key2 and the match length. *
                              - * - * @example If key1 holds the string "abcd1234" and key2 - * holds the string "bcdef1234" then the sample result would be - *
                              {@code
                              -     * new Object[] {
                              -     *     new Object[] {
                              -     *         new Long[] {4L, 7L},
                              -     *         new Long[] {5L, 8L},
                              -     *         4L
                              -     *     },
                              -     *     new Object[] {
                              -     *         new Long[] {1L, 3L},
                              -     *         new Long[] {0L, 2L},
                              -     *         3L
                              -     *     }
                              -     * }
                              -     * }
                              - * The result indicates that the first substring match is "1234" in key1 - * at index 4 to 7 which matches the substring in key2 - * at index 5 to 8 and the last element in the array is the - * length of the substring match which is 4. And the second substring match is - * "bcd" in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2 and - * the last element in the array is the length of the substring match which is 3. + * See example of {@link StringBaseCommands#lcsIdxWithMatchLen(String, String)} for more + * details. */ public T lcsIdxWithMatchLen(@NonNull ArgType key1, @NonNull ArgType key2) { checkTypeOrThrow(key1); @@ -6427,7 +6485,7 @@ public T lcsIdxWithMatchLen(@NonNull ArgType key1, @NonNull ArgType ke } /** - * Returns the indices and length of the longest common subsequence between strings stored at + * Returns the indices and lengths of the longest common subsequences between strings stored at * key1 and key2. * * @since Valkey 7.0 and above. @@ -6438,39 +6496,17 @@ public T lcsIdxWithMatchLen(@NonNull ArgType key1, @NonNull ArgType ke * @param key2 The key that stores the second string. * @param minMatchLen The minimum length of matches to include in the result. * @return Command Response - A Map containing the indices of the longest common - * subsequence between the 2 strings and the length of the longest common subsequence. The + * subsequence between the 2 strings and the lengths of the longest common subsequences. The * resulting map contains two keys, "matches" and "len": *
                                - *
                              • "len" is mapped to the length of the longest common subsequence between the 2 strings - * stored as Long. - *
                              • "matches" is mapped to a three dimensional Long array that stores pairs - * of indices that represent the location of the common subsequences in the strings held - * by key1 and key2. + *
                              • "len" is mapped to the total length of the all longest common subsequences between + * the 2 strings stored as Long. + *
                              • "matches" is mapped to a three dimensional array that stores pairs of indices that + * represent the location of the common subsequences in the strings held by key1 + * and key2 and the match length. *
                              - * - * @example If key1 holds the string "abcd1234" and key2 - * holds the string "bcdef1234" then the sample result would be - *
                              {@code
                              -     * new Object[] {
                              -     *     new Object[] {
                              -     *         new Long[] { 4L, 7L },
                              -     *         new Long[] { 5L, 8L },
                              -     *         4L
                              -     *     },
                              -     *     new Object[] {
                              -     *         new Long[] { 1L, 3L },
                              -     *         new Long[] { 0L, 2L },
                              -     *         3L
                              -     *     }
                              -     * }
                              -     * }
                              - * The result indicates that the first substring match is "1234" in key1 - * at index 4 to 7 which matches the substring in key2 - * at index 5 to 8 and the last element in the array is the - * length of the substring match which is 4. And the second substring match is - * "bcd" in key1 at index 1 to 3 which - * matches the substring in key2 at index 0 to 2 and - * the last element in the array is the length of the substring match which is 3. + * See example of {@link StringBaseCommands#lcsIdxWithMatchLen(String, String, long)} for more + * details. */ public T lcsIdxWithMatchLen( @NonNull ArgType key1, @NonNull ArgType key2, long minMatchLen) { diff --git a/java/client/src/main/java/glide/api/models/ClusterValue.java b/java/client/src/main/java/glide/api/models/ClusterValue.java index d141c3cd08..66240a241c 100644 --- a/java/client/src/main/java/glide/api/models/ClusterValue.java +++ b/java/client/src/main/java/glide/api/models/ClusterValue.java @@ -46,13 +46,16 @@ public T getSingleValue() { /** A constructor for the value with type auto-detection. */ @SuppressWarnings("unchecked") public static ClusterValue of(Object data) { - var res = new ClusterValue(); if (data instanceof Map) { - res.multiValue = (Map) data; + var map = (Map) data; + if (map.isEmpty() || map.keySet().toArray()[0] instanceof String) { + return ofMultiValue((Map) data); + } else { // GlideString + return ofMultiValueBinary((Map) data); + } } else { - res.singleValue = (T) data; + return ofSingleValue((T) data); } - return res; } /** A constructor for the value. */ @@ -73,10 +76,9 @@ public static ClusterValue ofMultiValue(Map data) { public static ClusterValue ofMultiValueBinary(Map data) { var res = new ClusterValue(); // the map node address can be converted to a string - Map multiValue = + res.multiValue = data.entrySet().stream() .collect(Collectors.toMap(e -> e.getKey().getString(), Map.Entry::getValue)); - res.multiValue = multiValue; return res; } diff --git a/java/client/src/main/java/glide/api/models/commands/RestoreOptions.java b/java/client/src/main/java/glide/api/models/commands/RestoreOptions.java index 4a069e5a92..a35bd40807 100644 --- a/java/client/src/main/java/glide/api/models/commands/RestoreOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/RestoreOptions.java @@ -11,9 +11,10 @@ /** * Optional arguments to {@link GenericBaseCommands#restore(GlideString, long, byte[], - * RestoreOptions)} + * RestoreOptions)}. * * @see valkey.io + * @apiNote IDLETIME and FREQ modifiers cannot be set at the same time. */ @Getter @Builder diff --git a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchOptions.java b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchOptions.java index 24cc1be42a..38dbaf399f 100644 --- a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchOptions.java @@ -11,11 +11,11 @@ * GeoSearchOrigin.SearchOrigin, GeoSearchShape, GeoSearchOptions)} command, options include: * *
                                - *
                              • WITHDIST: Also return the distance of the returned items from the specified center point. - * The distance is returned in the same unit as specified for the searchBy - * argument. - *
                              • WITHCOORD: Also return the coordinate of the returned items. - *
                              • WITHHASH: Also return the geohash of the returned items. for the general user. + *
                              • WITHDIST: Also return the distance of the returned items from the specified + * center point. The distance is returned in the same unit as specified for the searchBy + * argument. + *
                              • WITHCOORD: Also return the coordinate of the returned items. + *
                              • WITHHASH: Also return the geohash of the returned items. *
                              * * @see valkey.io diff --git a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchOrigin.java b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchOrigin.java index 9be1aa333a..0caebd9013 100644 --- a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchOrigin.java +++ b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchOrigin.java @@ -15,7 +15,7 @@ public final class GeoSearchOrigin { /** Valkey API keyword used to perform search from the position of a given member. */ public static final String FROMMEMBER_VALKEY_API = "FROMMEMBER"; - /** Valkey API keyword used to perform search from the given longtitude & latitue position. */ + /** Valkey API keyword used to perform search from the given longitude & latitude position. */ public static final String FROMLONLAT_VALKEY_API = "FROMLONLAT"; /** @@ -36,12 +36,6 @@ public interface SearchOrigin { public static class CoordOrigin implements SearchOrigin { private final GeospatialData position; - /** - * Converts GeoSearchOrigin into a String[]. - * - * @return String[] An array containing arguments corresponding to the starting point of the - * query. - */ public String[] toArgs() { return ArrayUtils.addAll(new String[] {FROMLONLAT_VALKEY_API}, position.toArgs()); } @@ -52,12 +46,6 @@ public String[] toArgs() { public static class MemberOrigin implements SearchOrigin { private final String member; - /** - * Converts GeoSearchOrigin into a String[]. - * - * @return String[] An array containing arguments corresponding to the starting point of the - * query. - */ public String[] toArgs() { return new String[] {FROMMEMBER_VALKEY_API, member}; } @@ -68,12 +56,6 @@ public String[] toArgs() { public static class MemberOriginBinary implements SearchOrigin { private final GlideString member; - /** - * Converts GeoSearchOrigin into a String[]. - * - * @return String[] An array containing arguments corresponding to the starting point of the - * query. - */ public String[] toArgs() { return new String[] {FROMMEMBER_VALKEY_API, member.toString()}; } diff --git a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchResultOptions.java b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchResultOptions.java index 5a4e6c8b29..a7aa7cc4bd 100644 --- a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchResultOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchResultOptions.java @@ -8,20 +8,16 @@ /** * Optional arguments for {@link GeospatialIndicesBaseCommands#geosearch(String, - * GeoSearchOrigin.SearchOrigin, GeoSearchShape, GeoSearchOptions)} command that contains up to 2 - * optional input, including: + * GeoSearchOrigin.SearchOrigin, GeoSearchShape, GeoSearchOptions)} command that contains up to 3 + * optional inputs, including: * *
                                - *
                              • SortOrder: The query's order to sort the results by: - *
                                  - *
                                • ASC: Sort returned items from the nearest to the farthest, relative to the center - * point. - *
                                • DESC: Sort returned items from the farthest to the nearest, relative to the center - * point. - *
                                - *
                              • COUNT: Limits the results to the first N matching items, when the 'ANY' option is used, the - * command returns as soon as enough matches are found. This means that the results might may - * not be the ones closest to the origin. + *
                              • {@link SortOrder} to order the search results by the distance to the center point of the + * search area. + *
                              • COUNT to limit the number of search results. + *
                              • ANY, which can only be used if COUNT is also provided. This + * option makes the command return as soon as enough matches are found. This means that the + * results might not be the ones closest to the origin. *
                              * * @see valkey.io @@ -42,38 +38,40 @@ public class GeoSearchResultOptions { /** Indicates the number of matches the result should be limited to. */ private final long count; - /** Indicates if the 'ANY' keyword is used for 'COUNT'. */ + /** Whether to allow returning as soon as enough matches are found. */ private final boolean isAny; - /** Constructor with count only. */ + /** Define number of search results. */ public GeoSearchResultOptions(long count) { this.sortOrder = null; this.count = count; this.isAny = false; } - /** Constructor with count and the 'ANY' keyword. */ + /** + * Define number of search results, and whether or not the ANY option should be used. + */ public GeoSearchResultOptions(long count, boolean isAny) { this.sortOrder = null; this.count = count; this.isAny = isAny; } - /** Constructor with sort order only. */ + /** Define the sort order only. */ public GeoSearchResultOptions(SortOrder order) { this.sortOrder = order; this.count = -1; this.isAny = false; } - /** Constructor with sort order and count. */ + /** Define the sort order and count. */ public GeoSearchResultOptions(SortOrder order, long count) { this.sortOrder = order; this.count = count; this.isAny = false; } - /** Constructor with sort order, count and 'ANY' keyword. */ + /** Configure all parameters. */ public GeoSearchResultOptions(SortOrder order, long count, boolean isAny) { this.sortOrder = order; this.count = count; diff --git a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchShape.java b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchShape.java index 3add2dadd9..75ffe7faf3 100644 --- a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchShape.java +++ b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchShape.java @@ -2,54 +2,33 @@ package glide.api.models.commands.geospatial; import glide.api.commands.GeospatialIndicesBaseCommands; -import lombok.Getter; /** * The query's shape for {@link GeospatialIndicesBaseCommands} command. * * @see valkey.io */ -@Getter public final class GeoSearchShape { - /** Valkey API keyword used to perform geosearch by radius. */ - public static final String BYRADIUS_VALKEY_API = "BYRADIUS"; - /** Valkey API keyword used to perform geosearch by box. */ - public static final String BYBOX_VALKEY_API = "BYBOX"; - - /** - * The geosearch query's shape: - * - *
                                - *
                              • BYRADIUS - Circular shaped query. - *
                              • BYBOX - Box shaped query. - *
                              - */ - public enum SearchShape { + /** The geosearch query's shape. */ + enum SearchShape { + /** Circular shaped query. */ BYRADIUS, + /** Rectangular shaped query. */ BYBOX } - /** The geosearch query's shape. */ private final SearchShape shape; - - /** The circular geosearch query's radius. */ private final double radius; - - /** The box geosearch query's width. */ private final double width; - - /** The box geosearch query's height. */ private final double height; - - /** The geosearch query's metric unit. */ private final GeoUnit unit; /** - * BYRADIUS constructor for GeoSearchShape + * Defines a circular search area. * * @param radius The radius to search by. - * @param unit The unit of the radius. + * @param unit The measurement unit of the radius. */ public GeoSearchShape(double radius, GeoUnit unit) { this.shape = SearchShape.BYRADIUS; @@ -62,11 +41,11 @@ public GeoSearchShape(double radius, GeoUnit unit) { } /** - * BYBOX constructor for GeoSearchShape + * Defines a rectangular search area. * * @param width The width to search by. * @param height The height to search by. - * @param unit The unit of the radius. + * @param unit The measurement unit of the width and height. */ public GeoSearchShape(double width, double height, GeoUnit unit) { this.shape = SearchShape.BYBOX; @@ -91,7 +70,7 @@ public String[] toArgs() { return new String[] { shape.toString(), Double.toString(width), Double.toString(height), unit.getValkeyAPI() }; - default: + default: // unreachable return new String[] {}; } } diff --git a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchStoreOptions.java b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchStoreOptions.java index ec2c50d34e..384567b56d 100644 --- a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchStoreOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoSearchStoreOptions.java @@ -12,15 +12,16 @@ */ @Builder public final class GeoSearchStoreOptions { - /** - * Valkey API keyword used to perform geosearchstore and optionally sort the results with their - * distance from the center. - */ + /** Valkey API keyword for {@link #storeDist} parameter. */ public static final String GEOSEARCHSTORE_VALKEY_API = "STOREDIST"; /** - * boolean value indicating if the STOREDIST option should be included. Can be included in builder - * construction by using {@link GeoSearchStoreOptionsBuilder#storedist()}. + * Determines what is stored as the sorted set score. Defaults to false.
                              + * If set to false, the geohash of the location will be stored as the sorted set + * score.
                              + * If set to true, the distance from the center of the shape (circle or box) will be + * stored as the sorted set score. The distance is represented as a floating-point number in the + * same unit specified for that shape. */ private final boolean storeDist; @@ -40,6 +41,7 @@ public String[] toArgs() { public static class GeoSearchStoreOptionsBuilder { public GeoSearchStoreOptionsBuilder() {} + /** Enable sorting the results with their distance from the center. */ public GeoSearchStoreOptionsBuilder storedist() { return storeDist(true); } diff --git a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoUnit.java b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoUnit.java index b81bcecdeb..913796f060 100644 --- a/java/client/src/main/java/glide/api/models/commands/geospatial/GeoUnit.java +++ b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoUnit.java @@ -1,14 +1,10 @@ /** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api.models.commands.geospatial; -import glide.api.commands.GeospatialIndicesBaseCommands; import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Enumeration representing distance units options for the {@link - * GeospatialIndicesBaseCommands#geodist(String, String, String, GeoUnit)} command. - */ +/** Enumeration representing distance units options for the geospatial command. */ @RequiredArgsConstructor @Getter public enum GeoUnit { diff --git a/java/client/src/main/java/glide/api/models/configuration/BackoffStrategy.java b/java/client/src/main/java/glide/api/models/configuration/BackoffStrategy.java index c2e8d0f0c1..bd95629866 100644 --- a/java/client/src/main/java/glide/api/models/configuration/BackoffStrategy.java +++ b/java/client/src/main/java/glide/api/models/configuration/BackoffStrategy.java @@ -4,6 +4,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NonNull; +import lombok.ToString; /** * Represents the strategy used to determine how and when to reconnect, in case of connection @@ -24,6 +25,7 @@ */ @Getter @Builder +@ToString public class BackoffStrategy { /** * Number of retry attempts that the client should perform when disconnected from the server, diff --git a/java/client/src/main/java/glide/api/models/configuration/ClusterSubscriptionConfiguration.java b/java/client/src/main/java/glide/api/models/configuration/ClusterSubscriptionConfiguration.java index a29ddd3d83..c45d6abb33 100644 --- a/java/client/src/main/java/glide/api/models/configuration/ClusterSubscriptionConfiguration.java +++ b/java/client/src/main/java/glide/api/models/configuration/ClusterSubscriptionConfiguration.java @@ -1,6 +1,8 @@ /** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api.models.configuration; +import static glide.api.models.GlideString.gs; + import glide.api.GlideClusterClient; import glide.api.models.GlideString; import java.util.HashMap; @@ -91,6 +93,17 @@ public ClusterSubscriptionConfigurationBuilder subscription( return this; } + /** + * Add a subscription to a channel or to multiple channels if {@link + * PubSubClusterChannelMode#PATTERN} is used.
                              + * See {@link ClusterSubscriptionConfiguration#subscriptions}. + */ + public ClusterSubscriptionConfigurationBuilder subscription( + PubSubClusterChannelMode mode, String channelOrPattern) { + addSubscription(subscriptions, mode, gs(channelOrPattern)); + return this; + } + /** * Set all subscriptions in a bulk. Rewrites previously stored configurations.
                              * See {@link ClusterSubscriptionConfiguration#subscriptions}. diff --git a/java/client/src/main/java/glide/api/models/configuration/GlideClientConfiguration.java b/java/client/src/main/java/glide/api/models/configuration/GlideClientConfiguration.java index 5c0f04c945..edb7bbb326 100644 --- a/java/client/src/main/java/glide/api/models/configuration/GlideClientConfiguration.java +++ b/java/client/src/main/java/glide/api/models/configuration/GlideClientConfiguration.java @@ -3,6 +3,7 @@ import glide.api.GlideClient; import lombok.Getter; +import lombok.ToString; import lombok.experimental.SuperBuilder; /** @@ -27,6 +28,7 @@ */ @Getter @SuperBuilder +@ToString public class GlideClientConfiguration extends BaseClientConfiguration { /** Strategy used to determine how and when to reconnect, in case of connection failures. */ private final BackoffStrategy reconnectStrategy; diff --git a/java/client/src/main/java/glide/api/models/configuration/NodeAddress.java b/java/client/src/main/java/glide/api/models/configuration/NodeAddress.java index b3766bad3b..b59ae39f7c 100644 --- a/java/client/src/main/java/glide/api/models/configuration/NodeAddress.java +++ b/java/client/src/main/java/glide/api/models/configuration/NodeAddress.java @@ -4,6 +4,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NonNull; +import lombok.ToString; /** * Represents the address and port of a node in the cluster or in standalone installation. @@ -17,6 +18,7 @@ */ @Getter @Builder +@ToString public class NodeAddress { public static final String DEFAULT_HOST = "localhost"; public static final Integer DEFAULT_PORT = 6379; diff --git a/java/client/src/main/java/glide/utils/ArrayTransformUtils.java b/java/client/src/main/java/glide/utils/ArrayTransformUtils.java index fccdac14f3..c89545faef 100644 --- a/java/client/src/main/java/glide/utils/ArrayTransformUtils.java +++ b/java/client/src/main/java/glide/utils/ArrayTransformUtils.java @@ -44,6 +44,44 @@ public static GlideString[] convertMapToKeyValueGlideStringArray(Map Stream.of(entry[0], entry[1])) + .toArray(String[]::new); + } + + /** + * Converts a nested array of GlideString keys and values of any type in to an array of + * GlideStrings with alternating keys and values. + * + * @param args Nested array of GlideString keys to values of any type to convert. + * @return Array of strings [key1, gs(value1.toString()), key2, gs(value2.toString()), ...]. + */ + public static GlideString[] convertNestedArrayToKeyValueGlideStringArray(GlideString[][] args) { + for (GlideString[] entry : args) { + if (entry.length != 2) { + throw new IllegalArgumentException( + "Array entry had the wrong length. Expected length 2 but got length " + entry.length); + } + } + return Arrays.stream(args) + .flatMap(entry -> Stream.of(entry[0], GlideString.gs(entry[1].toString()))) + .toArray(GlideString[]::new); + } + /** * Converts a map of string keys and values of any type into an array of strings with alternating * values and keys. @@ -250,6 +288,25 @@ public static GlideString[] flattenMapToGlideStringArray(Map args) { .toArray(GlideString[]::new); } + /** + * Converts a nested array of any type of keys and values in to an array of GlideString with + * alternating keys and values. + * + * @param args Nested array of keys to values of any type to convert. + * @return Array of GlideString [key1, value1, key2, value2, ...]. + */ + public static GlideString[] flattenNestedArrayToGlideStringArray(T[][] args) { + for (T[] entry : args) { + if (entry.length != 2) { + throw new IllegalArgumentException( + "Array entry had the wrong length. Expected length 2 but got length " + entry.length); + } + } + return Arrays.stream(args) + .flatMap(entry -> Stream.of(GlideString.of(entry[0]), GlideString.of(entry[1]))) + .toArray(GlideString[]::new); + } + /** * Converts a map of any type of keys and values in to an array of GlideString with alternating * values and keys. diff --git a/java/client/src/test/java/glide/api/GlideClientTest.java b/java/client/src/test/java/glide/api/GlideClientTest.java index 2727db7346..2170218b36 100644 --- a/java/client/src/test/java/glide/api/GlideClientTest.java +++ b/java/client/src/test/java/glide/api/GlideClientTest.java @@ -107,6 +107,9 @@ import static command_request.CommandRequestOuterClass.RequestType.PfCount; import static command_request.CommandRequestOuterClass.RequestType.PfMerge; import static command_request.CommandRequestOuterClass.RequestType.Ping; +import static command_request.CommandRequestOuterClass.RequestType.PubSubChannels; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumPat; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumSub; import static command_request.CommandRequestOuterClass.RequestType.Publish; import static command_request.CommandRequestOuterClass.RequestType.RPop; import static command_request.CommandRequestOuterClass.RequestType.RPush; @@ -271,6 +274,8 @@ import static glide.utils.ArrayTransformUtils.convertMapToKeyValueStringArray; import static glide.utils.ArrayTransformUtils.convertMapToValueKeyStringArray; import static glide.utils.ArrayTransformUtils.convertMapToValueKeyStringArrayBinary; +import static glide.utils.ArrayTransformUtils.convertNestedArrayToKeyValueGlideStringArray; +import static glide.utils.ArrayTransformUtils.convertNestedArrayToKeyValueStringArray; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; @@ -417,6 +422,30 @@ public void customCommand_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void customCommand_binary_returns_success() { + // setup + GlideString key = gs("testKey"); + Object value = "testValue"; + GlideString cmd = gs("GETSTRING"); + GlideString[] arguments = new GlideString[] {cmd, key}; + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(CustomCommand), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.customCommand(arguments); + String payload = (String) response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void exec() { @@ -7174,6 +7203,59 @@ public void xadd_returns_success() { assertEquals(returnId, response.get()); } + @SneakyThrows + @Test + public void xadd_nested_array_returns_success() { + // setup + String key = "testKey"; + String[][] fieldValues = {{"testField1", "testValue1"}, {"testField2", "testValue2"}}; + String[] fieldValuesArgs = convertNestedArrayToKeyValueStringArray(fieldValues); + String[] arguments = new String[] {key, "*"}; + arguments = ArrayUtils.addAll(arguments, fieldValuesArgs); + String returnId = "testId"; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(returnId); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(XAdd), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.xadd(key, fieldValues); + + // verify + assertEquals(testResponse, response); + assertEquals(returnId, response.get()); + } + + @SneakyThrows + @Test + public void xadd_nested_array_with_options_returns_success() { + // setup + String key = "testKey"; + String[][] fieldValues = {{"testField1", "testValue1"}, {"testField2", "testValue2"}}; + String[] fieldValuesArgs = convertNestedArrayToKeyValueStringArray(fieldValues); + String[] arguments = new String[] {key, "*"}; + arguments = ArrayUtils.addAll(arguments, fieldValuesArgs); + String returnId = "testId"; + StreamAddOptions options = StreamAddOptions.builder().build(); + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(returnId); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(XAdd), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.xadd(key, fieldValues, options); + + // verify + assertEquals(testResponse, response); + assertEquals(returnId, response.get()); + } + @SneakyThrows @Test public void xadd_binary_returns_success() { @@ -7202,6 +7284,63 @@ public void xadd_binary_returns_success() { assertEquals(returnId, response.get()); } + @SneakyThrows + @Test + public void xadd_nested_array_binary_returns_success() { + // setup + GlideString key = gs("testKey"); + GlideString[][] fieldValues = { + {gs("testField1"), gs("testValue1")}, {gs("testField2"), gs("testValue2")} + }; + GlideString[] fieldValuesArgs = convertNestedArrayToKeyValueGlideStringArray(fieldValues); + GlideString[] arguments = new GlideString[] {key, gs("*")}; + arguments = ArrayUtils.addAll(arguments, fieldValuesArgs); + GlideString returnId = gs("testId"); + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(returnId); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(XAdd), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.xadd(key, fieldValues); + + // verify + assertEquals(testResponse, response); + assertEquals(returnId, response.get()); + } + + @SneakyThrows + @Test + public void xadd_nested_array_with_options_binary_returns_success() { + // setup + GlideString key = gs("testKey"); + GlideString[][] fieldValues = { + {gs("testField1"), gs("testValue1")}, {gs("testField2"), gs("testValue2")} + }; + GlideString[] fieldValuesArgs = convertNestedArrayToKeyValueGlideStringArray(fieldValues); + GlideString[] arguments = new GlideString[] {key, gs("*")}; + arguments = ArrayUtils.addAll(arguments, fieldValuesArgs); + GlideString returnId = gs("testId"); + StreamAddOptionsBinary options = StreamAddOptionsBinary.builder().build(); + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(returnId); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(XAdd), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.xadd(key, fieldValues, options); + + // verify + assertEquals(testResponse, response); + assertEquals(returnId, response.get()); + } + private static List getStreamAddOptions() { return List.of( Arguments.of( @@ -11346,18 +11485,21 @@ public void functionKill_returns_success() { public void functionStats_returns_success() { // setup String[] args = new String[0]; - Map> value = Map.of("1", Map.of("2", 2)); - CompletableFuture>> testResponse = new CompletableFuture<>(); + Map>> value = + Map.of("::1", Map.of("1", Map.of("2", 2))); + CompletableFuture>>> testResponse = + new CompletableFuture<>(); testResponse.complete(value); // match on protobuf request - when(commandManager.>>submitNewCommand( + when(commandManager.>>>submitNewCommand( eq(FunctionStats), eq(args), any())) .thenReturn(testResponse); // exercise - CompletableFuture>> response = service.functionStats(); - Map> payload = response.get(); + CompletableFuture>>> response = + service.functionStats(); + Map>> payload = response.get(); // verify assertEquals(testResponse, response); @@ -11369,20 +11511,21 @@ public void functionStats_returns_success() { public void functionStatsBinary_returns_success() { // setup GlideString[] args = new GlideString[0]; - Map> value = Map.of(gs("1"), Map.of(gs("2"), 2)); - CompletableFuture>> testResponse = + Map>> value = + Map.of("::1", Map.of(gs("1"), Map.of(gs("2"), 2))); + CompletableFuture>>> testResponse = new CompletableFuture<>(); testResponse.complete(value); // match on protobuf request - when(commandManager.>>submitNewCommand( + when(commandManager.>>>submitNewCommand( eq(FunctionStats), eq(args), any())) .thenReturn(testResponse); // exercise - CompletableFuture>> response = + CompletableFuture>>> response = service.functionStatsBinary(); - Map> payload = response.get(); + Map>> payload = response.get(); // verify assertEquals(testResponse, response); @@ -13382,6 +13525,170 @@ public void publish_returns_success() { assertEquals(OK, payload); } + @SneakyThrows + @Test + public void pubsubChannels_returns_success() { + // setup + String[] arguments = new String[0]; + String[] value = new String[] {"ch1", "ch2"}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PubSubChannels), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pubsubChannels(); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pubsubChannelsBinary_returns_success() { + // setup + GlideString[] arguments = new GlideString[0]; + GlideString[] value = new GlideString[] {gs("ch1"), gs("ch2")}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PubSubChannels), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pubsubChannelsBinary(); + GlideString[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pubsubChannels_with_pattern_returns_success() { + // setup + String pattern = "ch*"; + String[] arguments = new String[] {pattern}; + String[] value = new String[] {"ch1", "ch2"}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PubSubChannels), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pubsubChannels(pattern); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pubsubChannelsBinary_with_pattern_returns_success() { + // setup + GlideString pattern = gs("ch*"); + GlideString[] arguments = new GlideString[] {pattern}; + GlideString[] value = new GlideString[] {gs("ch1"), gs("ch2")}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PubSubChannels), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pubsubChannels(pattern); + GlideString[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pubsubNumPat_returns_success() { + // setup + String[] arguments = new String[0]; + Long value = 42L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PubSubNumPat), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pubsubNumPat(); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pubsubNumSub_returns_success() { + // setup + String[] arguments = new String[] {"ch1", "ch2"}; + Map value = Map.of(); + + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.>submitNewCommand(eq(PubSubNumSub), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture> response = service.pubsubNumSub(arguments); + Map payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pubsubNumSubBinary_returns_success() { + // setup + GlideString[] arguments = new GlideString[] {gs("ch1"), gs("ch2")}; + Map value = Map.of(); + + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.>submitNewCommand( + eq(PubSubNumSub), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture> response = service.pubsubNumSub(arguments); + Map payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void sunion_returns_success() { diff --git a/java/client/src/test/java/glide/api/GlideClusterClientTest.java b/java/client/src/test/java/glide/api/GlideClusterClientTest.java index 54b49d9de2..648c64bf33 100644 --- a/java/client/src/test/java/glide/api/GlideClusterClientTest.java +++ b/java/client/src/test/java/glide/api/GlideClusterClientTest.java @@ -120,6 +120,42 @@ public void custom_command_returns_multi_value() { } } + @Test + @SneakyThrows + public void custom_command_binary_returns_single_value() { + var commandManager = new TestCommandManager(null); + + try (var client = new TestClient(commandManager, "TEST")) { + var value = client.customCommand(new GlideString[0]).get(); + assertEquals("TEST", value.getSingleValue()); + } + } + + @Test + @SneakyThrows + public void custom_command_binary_returns_multi_value() { + var commandManager = new TestCommandManager(null); + + var data = Map.of("key1", "value1", "key2", "value2"); + try (var client = new TestClient(commandManager, data)) { + var value = client.customCommand(new GlideString[0]).get(); + assertEquals(data, value.getMultiValue()); + } + } + + @Test + @SneakyThrows + public void custom_command_binary_returns_multi_binary_value() { + var commandManager = new TestCommandManager(null); + + var data = Map.of(gs("key1"), "value1", gs("key2"), "value2"); + var dataNormalized = Map.of("key1", "value1", "key2", "value2"); + try (var client = new TestClient(commandManager, data)) { + var value = client.customCommand(new GlideString[0]).get(); + assertEquals(dataNormalized, value.getMultiValue()); + } + } + @Test @SneakyThrows // test checks that even a map returned as a single value when single node route is used @@ -158,6 +194,45 @@ public void custom_command_returns_single_value_on_constant_response() { } } + @Test + @SneakyThrows + // test checks that even a map returned as a single value when single node route is used + public void custom_command_binary_with_single_node_route_returns_single_value() { + var commandManager = new TestCommandManager(null); + + var data = Map.of("key1", "value1", "key2", "value2"); + try (var client = new TestClient(commandManager, data)) { + var value = client.customCommand(new GlideString[0], RANDOM).get(); + assertEquals(data, value.getSingleValue()); + } + } + + @Test + @SneakyThrows + public void custom_command_binary_with_multi_node_route_returns_multi_value() { + var commandManager = new TestCommandManager(null); + + var data = Map.of(gs("key1"), "value1", gs("key2"), "value2"); + var dataNormalized = Map.of("key1", "value1", "key2", "value2"); + try (var client = new TestClient(commandManager, data)) { + var value = client.customCommand(new GlideString[0], ALL_NODES).get(); + assertEquals(dataNormalized, value.getMultiValue()); + } + } + + @Test + @SneakyThrows + public void custom_command_binary_returns_single_value_on_constant_response() { + var commandManager = + new TestCommandManager( + Response.newBuilder().setConstantResponse(ConstantResponse.OK).build()); + + try (var client = new TestClient(commandManager, "OK")) { + var value = client.customCommand(new GlideString[0], ALL_NODES).get(); + assertEquals("OK", value.getSingleValue()); + } + } + private static class TestClient extends GlideClusterClient { private final Object object; diff --git a/java/client/src/test/java/glide/api/models/ClusterValueTests.java b/java/client/src/test/java/glide/api/models/ClusterValueTests.java index d27bb1aaba..254abd49a4 100644 --- a/java/client/src/test/java/glide/api/models/ClusterValueTests.java +++ b/java/client/src/test/java/glide/api/models/ClusterValueTests.java @@ -41,6 +41,20 @@ public void handle_single_data() { assertThrows(Throwable.class, value::getMultiValue).getMessage())); } + @Test + public void handle_empty_map() { + var value = ClusterValue.of(Map.of()); + assertAll( + () -> assertTrue(value.hasMultiData()), + () -> assertFalse(value.hasSingleData()), + () -> assertNotNull(value.getMultiValue()), + () -> assertEquals(Map.of(), value.getMultiValue()), + () -> + assertEquals( + "No single value stored", + assertThrows(Throwable.class, value::getSingleValue).getMessage())); + } + @Test public void handle_multi_data() { var data = Map.of("node1", Map.of("config1", "param1", "config2", "param2"), "node2", Map.of()); @@ -56,6 +70,32 @@ public void handle_multi_data() { assertThrows(Throwable.class, value::getSingleValue).getMessage())); } + @Test + public void handle_multi_binary_data() { + var data = + Map.of( + gs("node1"), + Map.of(gs("config1"), gs("param1"), gs("config2"), gs("param2")), + gs("node2"), + Map.of()); + var dataNormalized = + Map.of( + "node1", + Map.of(gs("config1"), gs("param1"), gs("config2"), gs("param2")), + "node2", + Map.of()); + var value = ClusterValue.of(data); + assertAll( + () -> assertTrue(value.hasMultiData()), + () -> assertFalse(value.hasSingleData()), + () -> assertNotNull(value.getMultiValue()), + () -> assertEquals(dataNormalized, value.getMultiValue()), + () -> + assertEquals( + "No single value stored", + assertThrows(Throwable.class, value::getSingleValue).getMessage())); + } + @Test public void single_value_ctor() { var value = ClusterValue.ofSingleValue(Map.of("config1", "param1", "config2", "param2")); @@ -87,8 +127,8 @@ public void multi_value_binary_ctor() { () -> assertNotNull(value.getMultiValue()), // ofMultiValueBinary converts the key to a String, but the values are not converted () -> assertTrue(value.getMultiValue().containsKey("config1")), - () -> assertTrue(value.getMultiValue().get("config1").equals(gs("param1"))), + () -> assertEquals(gs("param1"), value.getMultiValue().get("config1")), () -> assertTrue(value.getMultiValue().containsKey("config2")), - () -> assertTrue(value.getMultiValue().get("config2").equals(gs("param2")))); + () -> assertEquals(gs("param2"), value.getMultiValue().get("config2"))); } } diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index 00aed8661f..c112ff7599 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -104,6 +104,9 @@ import static command_request.CommandRequestOuterClass.RequestType.PfCount; import static command_request.CommandRequestOuterClass.RequestType.PfMerge; import static command_request.CommandRequestOuterClass.RequestType.Ping; +import static command_request.CommandRequestOuterClass.RequestType.PubSubChannels; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumPat; +import static command_request.CommandRequestOuterClass.RequestType.PubSubNumSub; import static command_request.CommandRequestOuterClass.RequestType.Publish; import static command_request.CommandRequestOuterClass.RequestType.RPop; import static command_request.CommandRequestOuterClass.RequestType.RPush; @@ -769,6 +772,15 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), transaction.xadd("key", Map.of("field1", "foo1"), StreamAddOptions.builder().id("id").build()); results.add(Pair.of(XAdd, buildArgs("key", "id", "field1", "foo1"))); + transaction.xadd("key", new String[][] {new String[] {"field1", "foo1"}}); + results.add(Pair.of(XAdd, buildArgs("key", "*", "field1", "foo1"))); + + transaction.xadd( + "key", + new String[][] {new String[] {"field1", "foo1"}}, + StreamAddOptions.builder().id("id").build()); + results.add(Pair.of(XAdd, buildArgs("key", "id", "field1", "foo1"))); + transaction.xtrim("key", new MinId(true, "id")); results.add( Pair.of(XTrim, buildArgs("key", TRIM_MINID_VALKEY_API, TRIM_EXACT_VALKEY_API, "id"))); @@ -1269,6 +1281,18 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), transaction.publish("msg", "ch1"); results.add(Pair.of(Publish, buildArgs("ch1", "msg"))); + transaction.pubsubChannels(); + results.add(Pair.of(PubSubChannels, buildArgs())); + + transaction.pubsubChannels("pattern"); + results.add(Pair.of(PubSubChannels, buildArgs("pattern"))); + + transaction.pubsubNumPat(); + results.add(Pair.of(PubSubNumPat, buildArgs())); + + transaction.pubsubNumSub(new String[] {"ch1", "ch2"}); + results.add(Pair.of(PubSubNumSub, buildArgs("ch1", "ch2"))); + transaction.lcsIdx("key1", "key2"); results.add(Pair.of(LCS, buildArgs("key1", "key2", IDX_COMMAND_STRING))); diff --git a/java/integTest/build.gradle b/java/integTest/build.gradle index d6c7593820..ec7c4a2805 100644 --- a/java/integTest/build.gradle +++ b/java/integTest/build.gradle @@ -103,12 +103,53 @@ tasks.register('startStandalone') { tasks.register('getServerVersion') { doLast { - new ByteArrayOutputStream().withStream { os -> - exec { - commandLine 'redis-server', '-v' - standardOutput = os + def detectedVersion + def output = new ByteArrayOutputStream() + + // Helper method to find the full path of a command + def findFullPath = { command -> + def pathOutput = new ByteArrayOutputStream() + try { + exec { + commandLine 'which', command // Use 'where' for Windows + standardOutput = pathOutput + } + return pathOutput.toString().trim() + } catch (Exception e) { + println "Failed to find path for ${command}: ${e.message}" + return "" + } + } + + // Get full paths + def valkeyPath = findFullPath('valkey-server') + def redisPath = findFullPath('redis-server') + + def tryGetVersion = { serverPath -> + try { + exec { + commandLine serverPath, '-v' + standardOutput = output + } + return output.toString() + } catch (Exception e) { + println "Failed to execute ${serverPath}: ${e.message}" + return "" } - serverVersion = extractServerVersion(os.toString()) + } + + // Try valkey-server first, then redis-server if it fails + def versionOutput = tryGetVersion(valkeyPath) + if (versionOutput.isEmpty() && !redisPath.isEmpty()) { + versionOutput = tryGetVersion(redisPath) + } + + if (!versionOutput.isEmpty()) { + detectedVersion = extractServerVersion(versionOutput) + println "Detected server version: ${detectedVersion}" + serverVersion = detectedVersion + } else { + throw new GradleException("Failed to retrieve the server version.") } } } diff --git a/java/integTest/src/test/java/glide/PubSubTests.java b/java/integTest/src/test/java/glide/PubSubTests.java index 6ca9b3691f..19166cbbf0 100644 --- a/java/integTest/src/test/java/glide/PubSubTests.java +++ b/java/integTest/src/test/java/glide/PubSubTests.java @@ -2,6 +2,7 @@ package glide; import static glide.TestConfiguration.SERVER_VERSION; +import static glide.TestUtilities.assertDeepEquals; import static glide.TestUtilities.commonClientConfig; import static glide.TestUtilities.commonClusterClientConfig; import static glide.api.BaseClient.OK; @@ -27,6 +28,8 @@ import glide.api.models.configuration.BaseSubscriptionConfiguration.MessageCallback; import glide.api.models.configuration.ClusterSubscriptionConfiguration; import glide.api.models.configuration.ClusterSubscriptionConfiguration.PubSubClusterChannelMode; +import glide.api.models.configuration.RequestRoutingConfiguration.SlotKeyRoute; +import glide.api.models.configuration.RequestRoutingConfiguration.SlotType; import glide.api.models.configuration.StandaloneSubscriptionConfiguration; import glide.api.models.configuration.StandaloneSubscriptionConfiguration.PubSubChannelMode; import glide.api.models.exceptions.ConfigurationError; @@ -49,8 +52,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.SneakyThrows; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.tuple.Pair; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -111,10 +115,9 @@ private BaseClient createClientWithSubscriptions( @SneakyThrows private BaseClient createClient(boolean standalone) { - if (standalone) { - return GlideClient.createClient(commonClientConfig().build()).get(); - } - return GlideClusterClient.createClient(commonClusterClientConfig().build()).get(); + return standalone + ? GlideClient.createClient(commonClientConfig().build()).get() + : GlideClusterClient.createClient(commonClusterClientConfig().build()).get(); } /** @@ -128,17 +131,23 @@ private BaseClient createClient(boolean standalone) { private static final int MESSAGE_DELIVERY_DELAY = 500; // ms - @BeforeEach + @AfterEach @SneakyThrows public void cleanup() { for (var client : clients) { if (client instanceof GlideClusterClient) { - ((GlideClusterClient) client).customCommand(new String[] {"unsubscribe"}, ALL_NODES).get(); - ((GlideClusterClient) client).customCommand(new String[] {"punsubscribe"}, ALL_NODES).get(); - ((GlideClusterClient) client).customCommand(new String[] {"sunsubscribe"}, ALL_NODES).get(); + ((GlideClusterClient) client) + .customCommand(new GlideString[] {gs("unsubscribe")}, ALL_NODES) + .get(); + ((GlideClusterClient) client) + .customCommand(new GlideString[] {gs("punsubscribe")}, ALL_NODES) + .get(); + ((GlideClusterClient) client) + .customCommand(new GlideString[] {gs("sunsubscribe")}, ALL_NODES) + .get(); } else { - ((GlideClient) client).customCommand(new String[] {"unsubscribe"}).get(); - ((GlideClient) client).customCommand(new String[] {"punsubscribe"}).get(); + ((GlideClient) client).customCommand(new GlideString[] {gs("unsubscribe")}).get(); + ((GlideClient) client).customCommand(new GlideString[] {gs("punsubscribe")}).get(); } client.close(); } @@ -1232,7 +1241,7 @@ public void pubsub_with_binary(boolean standalone) { createClientWithSubscriptions( standalone, subscriptions, Optional.of(callback), Optional.of(callbackMessages)); var sender = createClient(standalone); - clients.addAll(Arrays.asList(listener, listener2, sender)); + clients.addAll(List.of(listener, listener2, sender)); assertEquals(OK, sender.publish(message.getMessage(), channel).get()); Thread.sleep(MESSAGE_DELIVERY_DELAY); // deliver the messages @@ -1241,4 +1250,222 @@ public void pubsub_with_binary(boolean standalone) { assertEquals(1, callbackMessages.size()); assertEquals(message, callbackMessages.get(0)); } + + @SneakyThrows + @ParameterizedTest(name = "standalone = {0}") + @ValueSource(booleans = {true, false}) + public void pubsub_channels(boolean standalone) { + assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0"), "This feature added in version 7"); + + // no channels exists yet + var client = createClient(standalone); + assertEquals(0, client.pubsubChannels().get().length); + assertEquals(0, client.pubsubChannelsBinary().get().length); + assertEquals(0, client.pubsubChannels("**").get().length); + assertEquals(0, client.pubsubChannels(gs("**")).get().length); + + var channels = Set.of("test_channel1", "test_channel2", "some_channel"); + String pattern = "test_*"; + + Map> subscriptions = + standalone + ? Map.of( + PubSubChannelMode.EXACT, + channels.stream().map(GlideString::gs).collect(Collectors.toSet())) + : Map.of( + PubSubClusterChannelMode.EXACT, + channels.stream().map(GlideString::gs).collect(Collectors.toSet())); + + var listener = createClientWithSubscriptions(standalone, subscriptions); + clients.addAll(List.of(client, listener)); + + // test without pattern + assertEquals(channels, Set.of(client.pubsubChannels().get())); + assertEquals(channels, Set.of(listener.pubsubChannels().get())); + assertEquals( + channels.stream().map(GlideString::gs).collect(Collectors.toSet()), + Set.of(client.pubsubChannelsBinary().get())); + assertEquals( + channels.stream().map(GlideString::gs).collect(Collectors.toSet()), + Set.of(listener.pubsubChannelsBinary().get())); + + // test with pattern + assertEquals( + Set.of("test_channel1", "test_channel2"), Set.of(client.pubsubChannels(pattern).get())); + assertEquals( + Set.of(gs("test_channel1"), gs("test_channel2")), + Set.of(client.pubsubChannels(gs(pattern)).get())); + assertEquals( + Set.of("test_channel1", "test_channel2"), Set.of(listener.pubsubChannels(pattern).get())); + assertEquals( + Set.of(gs("test_channel1"), gs("test_channel2")), + Set.of(listener.pubsubChannels(gs(pattern)).get())); + + // test with non-matching pattern + assertEquals(0, client.pubsubChannels("non_matching_*").get().length); + assertEquals(0, client.pubsubChannels(gs("non_matching_*")).get().length); + assertEquals(0, listener.pubsubChannels("non_matching_*").get().length); + assertEquals(0, listener.pubsubChannels(gs("non_matching_*")).get().length); + } + + @SneakyThrows + @ParameterizedTest(name = "standalone = {0}") + @ValueSource(booleans = {true, false}) + public void pubsub_numpat(boolean standalone) { + assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0"), "This feature added in version 7"); + + // no channels exists yet + var client = createClient(standalone); + assertEquals(0, client.pubsubNumPat().get()); + + var patterns = Set.of("news.*", "announcements.*"); + + Map> subscriptions = + standalone + ? Map.of( + PubSubChannelMode.PATTERN, + patterns.stream().map(GlideString::gs).collect(Collectors.toSet())) + : Map.of( + PubSubClusterChannelMode.PATTERN, + patterns.stream().map(GlideString::gs).collect(Collectors.toSet())); + + var listener = createClientWithSubscriptions(standalone, subscriptions); + clients.addAll(List.of(client, listener)); + + assertEquals(2, client.pubsubNumPat().get()); + assertEquals(2, listener.pubsubNumPat().get()); + } + + @SneakyThrows + @ParameterizedTest(name = "standalone = {0}") + @ValueSource(booleans = {true, false}) + public void pubsub_numsub(boolean standalone) { + assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0"), "This feature added in version 7"); + + // no channels exists yet + var client = createClient(standalone); + var channels = new String[] {"channel1", "channel2", "channel3"}; + assertEquals( + Arrays.stream(channels).collect(Collectors.toMap(c -> c, c -> 0L)), + client.pubsubNumSub(channels).get()); + + Map> subscriptions1 = + standalone + ? Map.of( + PubSubChannelMode.EXACT, Set.of(gs("channel1"), gs("channel2"), gs("channel3"))) + : Map.of( + PubSubClusterChannelMode.EXACT, + Set.of(gs("channel1"), gs("channel2"), gs("channel3"))); + var listener1 = createClientWithSubscriptions(standalone, subscriptions1); + + Map> subscriptions2 = + standalone + ? Map.of(PubSubChannelMode.EXACT, Set.of(gs("channel2"), gs("channel3"))) + : Map.of(PubSubClusterChannelMode.EXACT, Set.of(gs("channel2"), gs("channel3"))); + var listener2 = createClientWithSubscriptions(standalone, subscriptions2); + + Map> subscriptions3 = + standalone + ? Map.of(PubSubChannelMode.EXACT, Set.of(gs("channel3"))) + : Map.of(PubSubClusterChannelMode.EXACT, Set.of(gs("channel3"))); + var listener3 = createClientWithSubscriptions(standalone, subscriptions3); + + Map> subscriptions4 = + standalone + ? Map.of(PubSubChannelMode.PATTERN, Set.of(gs("channel*"))) + : Map.of(PubSubClusterChannelMode.PATTERN, Set.of(gs("channel*"))); + var listener4 = createClientWithSubscriptions(standalone, subscriptions4); + + clients.addAll(List.of(client, listener1, listener2, listener3, listener4)); + + var expected = Map.of("channel1", 1L, "channel2", 2L, "channel3", 3L, "channel4", 0L); + assertEquals(expected, client.pubsubNumSub(ArrayUtils.addFirst(channels, "channel4")).get()); + assertEquals(expected, listener1.pubsubNumSub(ArrayUtils.addFirst(channels, "channel4")).get()); + + var expectedGs = + Map.of(gs("channel1"), 1L, gs("channel2"), 2L, gs("channel3"), 3L, gs("channel4"), 0L); + assertEquals( + expectedGs, + client + .pubsubNumSub( + new GlideString[] {gs("channel1"), gs("channel2"), gs("channel3"), gs("channel4")}) + .get()); + assertEquals( + expectedGs, + listener2 + .pubsubNumSub( + new GlideString[] {gs("channel1"), gs("channel2"), gs("channel3"), gs("channel4")}) + .get()); + } + + @SneakyThrows + @ParameterizedTest(name = "standalone = {0}") + @ValueSource(booleans = {true, false}) + public void pubsub_channels_and_numpat_and_numsub_in_transaction(boolean standalone) { + assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0"), "This feature added in version 7"); + + var prefix = "{boo}-"; + var route = new SlotKeyRoute(prefix, SlotType.PRIMARY); + var client = createClient(standalone); + var channels = + new String[] {prefix + "test_channel1", prefix + "test_channel2", prefix + "some_channel"}; + var patterns = Set.of(prefix + "news.*", prefix + "announcements.*"); + String pattern = prefix + "test_*"; + + var transaction = + (standalone ? new Transaction() : new ClusterTransaction()) + .pubsubChannels() + .pubsubChannels(pattern) + .pubsubNumPat() + .pubsubNumSub(channels); + // no channels exists yet + var result = + standalone + ? ((GlideClient) client).exec((Transaction) transaction).get() + : ((GlideClusterClient) client).exec((ClusterTransaction) transaction, route).get(); + assertDeepEquals( + new Object[] { + new String[0], // pubsubChannels() + new String[0], // pubsubChannels(pattern) + 0L, // pubsubNumPat() + Arrays.stream(channels) + .collect(Collectors.toMap(c -> c, c -> 0L)), // pubsubNumSub(channels) + }, + result); + + Map> subscriptions = + standalone + ? Map.of( + PubSubChannelMode.EXACT, + Arrays.stream(channels).map(GlideString::gs).collect(Collectors.toSet()), + PubSubChannelMode.PATTERN, + patterns.stream().map(GlideString::gs).collect(Collectors.toSet())) + : Map.of( + PubSubClusterChannelMode.EXACT, + Arrays.stream(channels).map(GlideString::gs).collect(Collectors.toSet()), + PubSubClusterChannelMode.PATTERN, + patterns.stream().map(GlideString::gs).collect(Collectors.toSet())); + + var listener = createClientWithSubscriptions(standalone, subscriptions); + clients.addAll(List.of(client, listener)); + + result = + standalone + ? ((GlideClient) client).exec((Transaction) transaction).get() + : ((GlideClusterClient) client).exec((ClusterTransaction) transaction, route).get(); + + // convert arrays to sets, because we can't compare arrays - they received reordered + result[0] = Set.of((Object[]) result[0]); + result[1] = Set.of((Object[]) result[1]); + + assertDeepEquals( + new Object[] { + Set.of(channels), // pubsubChannels() + Set.of("{boo}-test_channel1", "{boo}-test_channel2"), // pubsubChannels(pattern) + 2L, // pubsubNumPat() + Arrays.stream(channels) + .collect(Collectors.toMap(c -> c, c -> 1L)), // pubsubNumSub(channels) + }, + result); + } } diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 3a93cb7939..aab628e7de 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -1438,7 +1438,7 @@ public void hrandfield(BaseClient client) { assertEquals(data.get(pair[0]), pair[1]); } - // Key exists, but it is not a List + // Key exists, but it is not a hash assertEquals(OK, client.set(key2, "value").get()); Exception executionException = assertThrows(ExecutionException.class, () -> client.hrandfield(key2).get()); @@ -5769,6 +5769,112 @@ public void bzmpop_binary_timeout_check(BaseClient client) { } } + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void xadd_duplicate_entry_keys(BaseClient client) { + String key = UUID.randomUUID().toString(); + String field = UUID.randomUUID().toString(); + String foo1 = "foo1"; + String bar1 = "bar1"; + + String[][] entry = new String[][] {{field, foo1}, {field, bar1}}; + String streamId = client.xadd(key, entry).get(); + // get everything from the stream + Map result = client.xrange(key, InfRangeBound.MIN, InfRangeBound.MAX).get(); + assertEquals(1, result.size()); + String[][] actualEntry = result.get(streamId); + assertDeepEquals(entry, actualEntry); + } + + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void xadd_duplicate_entry_keys_with_options(BaseClient client) { + String key = UUID.randomUUID().toString(); + String field = UUID.randomUUID().toString(); + String foo1 = "foo1"; + String bar1 = "bar1"; + + String[][] entry = new String[][] {{field, foo1}, {field, bar1}}; + String streamId = client.xadd(key, entry, StreamAddOptions.builder().build()).get(); + // get everything from the stream + Map result = client.xrange(key, InfRangeBound.MIN, InfRangeBound.MAX).get(); + assertEquals(1, result.size()); + String[][] actualEntry = result.get(streamId); + assertDeepEquals(entry, actualEntry); + } + + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void xadd_duplicate_entry_keys_binary(BaseClient client) { + GlideString key = gs(UUID.randomUUID().toString()); + GlideString field = gs(UUID.randomUUID().toString()); + GlideString foo1 = gs("foo1"); + GlideString bar1 = gs("bar1"); + + GlideString[][] entry = new GlideString[][] {{field, foo1}, {field, bar1}}; + GlideString streamId = client.xadd(key, entry).get(); + // get everything from the stream + Map result = + client.xrange(key, InfRangeBound.MIN, InfRangeBound.MAX).get(); + assertEquals(1, result.size()); + GlideString[][] actualEntry = result.get(streamId); + assertDeepEquals(entry, actualEntry); + } + + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void xadd_duplicate_entry_keys_with_options_binary(BaseClient client) { + GlideString key = gs(UUID.randomUUID().toString()); + GlideString field = gs(UUID.randomUUID().toString()); + GlideString foo1 = gs("foo1"); + GlideString bar1 = gs("bar1"); + + GlideString[][] entry = new GlideString[][] {{field, foo1}, {field, bar1}}; + GlideString streamId = client.xadd(key, entry, StreamAddOptionsBinary.builder().build()).get(); + // get everything from the stream + Map result = + client.xrange(key, InfRangeBound.MIN, InfRangeBound.MAX).get(); + assertEquals(1, result.size()); + GlideString[][] actualEntry = result.get(streamId); + assertDeepEquals(entry, actualEntry); + } + + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void xadd_wrong_length_entries(BaseClient client) { + String key = UUID.randomUUID().toString(); + String timestamp = "0-1"; + + // Entry too long + assertThrows( + IllegalArgumentException.class, + () -> + client + .xadd( + key, + new String[][] { + new String[] {"field1", "foo1"}, new String[] {"field2", "bar2", "oh no"} + }, + StreamAddOptions.builder().id(timestamp).build()) + .get()); + + // Entry too short + assertThrows( + IllegalArgumentException.class, + () -> + client + .xadd( + key, + new String[][] {new String[] {"field1", "foo1"}, new String[] {"oh no"}}, + StreamAddOptions.builder().id(timestamp).build()) + .get()); + } + @SneakyThrows @ParameterizedTest(autoCloseArguments = false) @MethodSource("getClients") @@ -6292,6 +6398,12 @@ public void xrange_and_xrevrange(BaseClient client) { assertEquals(1, newRevResult.size()); assertNotNull(newRevResult.get(streamId3)); + // xrange, xrevrange should return null with a zero/negative count + assertNull(client.xrange(key, InfRangeBound.MIN, InfRangeBound.MAX, 0L).get()); + assertNull(client.xrevrange(key, InfRangeBound.MAX, InfRangeBound.MIN, 0L).get()); + assertNull(client.xrange(key, InfRangeBound.MIN, InfRangeBound.MAX, -5L).get()); + assertNull(client.xrevrange(key, InfRangeBound.MAX, InfRangeBound.MIN, -1L).get()); + // xrange against an emptied stream assertEquals(3, client.xdel(key, new String[] {streamId1, streamId2, streamId3}).get()); Map emptiedResult = @@ -12482,8 +12594,8 @@ public void geosearch(BaseClient client) { String key1 = "{key}-1" + UUID.randomUUID(); String key2 = "{key}-2" + UUID.randomUUID(); String[] members = {"Catania", "Palermo", "edge2", "edge1"}; - Set members_set = Set.of(members); - GeospatialData[] members_coordinates = { + Set membersSet = Set.of(members); + GeospatialData[] membersCoordinates = { new GeospatialData(15.087269, 37.502669), new GeospatialData(13.361389, 38.115556), new GeospatialData(17.241510, 38.788135), @@ -12524,18 +12636,18 @@ public void geosearch(BaseClient client) { key1, Map.of( members[0], - members_coordinates[0], + membersCoordinates[0], members[1], - members_coordinates[1], + membersCoordinates[1], members[2], - members_coordinates[2], + membersCoordinates[2], members[3], - members_coordinates[3])) + membersCoordinates[3])) .get()); // Search by box, unit: km, from a geospatial data point assertTrue( - members_set.containsAll( + membersSet.containsAll( Set.of( client .geosearch( @@ -12620,26 +12732,26 @@ public void geosearch(BaseClient client) { .get()[0]); // test search by radius, units: feet, from member - double feet_radius = 200 * 3280.8399; + double feetRadius = 200 * 3280.8399; assertArrayEquals( new String[] {"Catania", "Palermo"}, client .geosearch( key1, new MemberOrigin("Catania"), - new GeoSearchShape(feet_radius, GeoUnit.FEET), + new GeoSearchShape(feetRadius, GeoUnit.FEET), new GeoSearchResultOptions(SortOrder.ASC)) .get()); // Test search by radius, unit: meters, from member - double meters_radius = 200 * 1000; + double metersRadius = 200 * 1000; assertArrayEquals( new String[] {"Palermo", "Catania"}, client .geosearch( key1, new MemberOrigin("Catania"), - new GeoSearchShape(meters_radius, GeoUnit.METERS), + new GeoSearchShape(metersRadius, GeoUnit.METERS), new GeoSearchResultOptions(SortOrder.DESC)) .get()); assertDeepEquals( @@ -12651,7 +12763,7 @@ public void geosearch(BaseClient client) { .geosearch( key1, new MemberOriginBinary(gs("Catania")), - new GeoSearchShape(meters_radius, GeoUnit.METERS), + new GeoSearchShape(metersRadius, GeoUnit.METERS), GeoSearchOptions.builder().withhash().build()) .get()); @@ -12763,8 +12875,8 @@ public void geosearch_binary(BaseClient client) { GlideString key1 = gs("{key}-1" + UUID.randomUUID()); GlideString key2 = gs("{key}-2" + UUID.randomUUID()); GlideString[] members = {gs("Catania"), gs("Palermo"), gs("edge2"), gs("edge1")}; - Set members_set = Set.of(members); - GeospatialData[] members_coordinates = { + Set membersSet = Set.of(members); + GeospatialData[] membersCoordinates = { new GeospatialData(15.087269, 37.502669), new GeospatialData(13.361389, 38.115556), new GeospatialData(17.241510, 38.788135), @@ -12805,18 +12917,18 @@ public void geosearch_binary(BaseClient client) { key1, Map.of( members[0], - members_coordinates[0], + membersCoordinates[0], members[1], - members_coordinates[1], + membersCoordinates[1], members[2], - members_coordinates[2], + membersCoordinates[2], members[3], - members_coordinates[3])) + membersCoordinates[3])) .get()); // Search by box, unit: km, from a geospatial data point assertTrue( - members_set.containsAll( + membersSet.containsAll( Set.of( client .geosearch( @@ -12901,26 +13013,26 @@ public void geosearch_binary(BaseClient client) { .get()[0]); // test search by radius, units: feet, from member - double feet_radius = 200 * 3280.8399; + double feetRadius = 200 * 3280.8399; assertArrayEquals( new GlideString[] {gs("Catania"), gs("Palermo")}, client .geosearch( key1, new MemberOriginBinary(gs("Catania")), - new GeoSearchShape(feet_radius, GeoUnit.FEET), + new GeoSearchShape(feetRadius, GeoUnit.FEET), new GeoSearchResultOptions(SortOrder.ASC)) .get()); // Test search by radius, unit: meters, from member - double meters_radius = 200 * 1000; + double metersRadius = 200 * 1000; assertArrayEquals( new GlideString[] {gs("Palermo"), gs("Catania")}, client .geosearch( key1, new MemberOriginBinary(gs("Catania")), - new GeoSearchShape(meters_radius, GeoUnit.METERS), + new GeoSearchShape(metersRadius, GeoUnit.METERS), new GeoSearchResultOptions(SortOrder.DESC)) .get()); @@ -12933,7 +13045,7 @@ public void geosearch_binary(BaseClient client) { .geosearch( key1, new MemberOriginBinary(gs("Catania")), - new GeoSearchShape(meters_radius, GeoUnit.METERS), + new GeoSearchShape(metersRadius, GeoUnit.METERS), GeoSearchOptions.builder().withhash().build()) .get()); // Test search by radius, unit: miles, from geospatial data @@ -13662,9 +13774,14 @@ public void sscan(BaseClient client) { assertDeepEquals(new String[] {}, result[resultCollectionIndex]); // Negative cursor - result = client.sscan(key1, "-1").get(); - assertEquals(initialCursor, result[resultCursorIndex]); - assertDeepEquals(new String[] {}, result[resultCollectionIndex]); + if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.sscan(key1, "-1").get()); + } else { + result = client.sscan(key1, "-1").get(); + assertEquals(initialCursor, result[resultCursorIndex]); + assertDeepEquals(new String[] {}, result[resultCollectionIndex]); + } // Result contains the whole set assertEquals(charMembers.length, client.sadd(key1, charMembers).get()); @@ -13798,9 +13915,14 @@ public void sscan_binary(BaseClient client) { assertDeepEquals(new GlideString[] {}, result[resultCollectionIndex]); // Negative cursor - result = client.sscan(key1, gs("-1")).get(); - assertEquals(initialCursor, gs(result[resultCursorIndex].toString())); - assertDeepEquals(new GlideString[] {}, result[resultCollectionIndex]); + if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.sscan(key1, gs("-1")).get()); + } else { + result = client.sscan(key1, gs("-1")).get(); + assertEquals(initialCursor, gs(result[resultCursorIndex].toString())); + assertDeepEquals(new GlideString[] {}, result[resultCollectionIndex]); + } // Result contains the whole set assertEquals(charMembers.length, client.sadd(key1, charMembers).get()); @@ -13947,9 +14069,14 @@ public void zscan(BaseClient client) { assertDeepEquals(new String[] {}, result[resultCollectionIndex]); // Negative cursor - result = client.zscan(key1, "-1").get(); - assertEquals(initialCursor, result[resultCursorIndex]); - assertDeepEquals(new String[] {}, result[resultCollectionIndex]); + if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.zscan(key1, "-1").get()); + } else { + result = client.zscan(key1, "-1").get(); + assertEquals(initialCursor, result[resultCursorIndex]); + assertDeepEquals(new String[] {}, result[resultCollectionIndex]); + } // Result contains the whole set assertEquals(charMembers.length, client.zadd(key1, charMap).get()); @@ -14128,9 +14255,14 @@ public void zscan_binary(BaseClient client) { assertDeepEquals(new GlideString[] {}, result[resultCollectionIndex]); // Negative cursor - result = client.zscan(key1, gs("-1")).get(); - assertEquals(initialCursor, gs(result[resultCursorIndex].toString())); - assertDeepEquals(new GlideString[] {}, result[resultCollectionIndex]); + if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.zscan(key1, gs("-1")).get()); + } else { + result = client.zscan(key1, gs("-1")).get(); + assertEquals(initialCursor, gs(result[resultCursorIndex].toString())); + assertDeepEquals(new GlideString[] {}, result[resultCollectionIndex]); + } // Result contains the whole set assertEquals(charMembers.length, client.zadd(key1.toString(), charMap_strings).get()); @@ -14313,9 +14445,14 @@ public void hscan(BaseClient client) { assertDeepEquals(new String[] {}, result[resultCollectionIndex]); // Negative cursor - result = client.hscan(key1, "-1").get(); - assertEquals(initialCursor, result[resultCursorIndex]); - assertDeepEquals(new String[] {}, result[resultCollectionIndex]); + if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.hscan(key1, "-1").get()); + } else { + result = client.hscan(key1, "-1").get(); + assertEquals(initialCursor, result[resultCursorIndex]); + assertDeepEquals(new String[] {}, result[resultCollectionIndex]); + } // Result contains the whole set assertEquals(charMembers.length, client.hset(key1, charMap).get()); @@ -14477,9 +14614,14 @@ public void hscan_binary(BaseClient client) { assertDeepEquals(new GlideString[] {}, result[resultCollectionIndex]); // Negative cursor - result = client.hscan(key1, gs("-1")).get(); - assertEquals(initialCursor, gs(result[resultCursorIndex].toString())); - assertDeepEquals(new GlideString[] {}, result[resultCollectionIndex]); + if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.hscan(key1, gs("-1")).get()); + } else { + result = client.hscan(key1, gs("-1")).get(); + assertEquals(initialCursor, gs(result[resultCursorIndex].toString())); + assertDeepEquals(new GlideString[] {}, result[resultCollectionIndex]); + } // Result contains the whole set assertEquals(charMembers.length, client.hset(key1, charMap).get()); diff --git a/java/integTest/src/test/java/glide/TestUtilities.java b/java/integTest/src/test/java/glide/TestUtilities.java index 55d5a69d55..97c5222ba7 100644 --- a/java/integTest/src/test/java/glide/TestUtilities.java +++ b/java/integTest/src/test/java/glide/TestUtilities.java @@ -30,10 +30,10 @@ @UtilityClass public class TestUtilities { /** Extract integer parameter value from INFO command output */ - public static int getValueFromInfo(String data, String value) { + public static long getValueFromInfo(String data, String value) { for (var line : data.split("\r\n")) { if (line.contains(value)) { - return Integer.parseInt(line.split(":")[1]); + return Long.parseLong(line.split(":")[1]); } } fail(); @@ -323,8 +323,8 @@ public static GlideString generateLuaLibCodeBinary( } /** - * Create a lua lib with a RO function which runs an endless loop up to timeout sec.
                              - * Execution takes at least 5 sec regardless of the timeout configured.
                              + * Create a lua lib with a function which runs an endless loop up to timeout sec.
                              + * Execution takes at least 5 sec regardless of the timeout configured. */ public static String createLuaLibWithLongRunningFunction( String libName, String funcName, int timeout, boolean readOnly) { diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 110e71e29b..c874dbb3b1 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -815,6 +815,7 @@ private static Object[] streamCommands(BaseTransaction transaction) { final String streamKey1 = "{streamKey}-1-" + UUID.randomUUID(); final String streamKey2 = "{streamKey}-2-" + UUID.randomUUID(); final String streamKey3 = "{streamKey}-3-" + UUID.randomUUID(); + final String streamKey4 = "{streamKey}-4-" + UUID.randomUUID(); final String groupName1 = "{groupName}-1-" + UUID.randomUUID(); final String groupName2 = "{groupName}-2-" + UUID.randomUUID(); final String groupName3 = "{groupName}-2-" + UUID.randomUUID(); @@ -824,6 +825,10 @@ private static Object[] streamCommands(BaseTransaction transaction) { .xadd(streamKey1, Map.of("field1", "value1"), StreamAddOptions.builder().id("0-1").build()) .xadd(streamKey1, Map.of("field2", "value2"), StreamAddOptions.builder().id("0-2").build()) .xadd(streamKey1, Map.of("field3", "value3"), StreamAddOptions.builder().id("0-3").build()) + .xadd( + streamKey4, + new String[][] {{"field4", "value4"}, {"field4", "value5"}}, + StreamAddOptions.builder().id("0-4").build()) .xlen(streamKey1) .xread(Map.of(streamKey1, "0-2")) .xread(Map.of(streamKey1, "0-2"), StreamReadOptions.builder().count(1L).build()) @@ -896,6 +901,8 @@ private static Object[] streamCommands(BaseTransaction transaction) { "0-1", // xadd(streamKey1, Map.of("field1", "value1"), ... .id("0-1").build()); "0-2", // xadd(streamKey1, Map.of("field2", "value2"), ... .id("0-2").build()); "0-3", // xadd(streamKey1, Map.of("field3", "value3"), ... .id("0-3").build()); + "0-4", // xadd(streamKey4, new String[][] {{"field4", "value4"}, {"field4", "value5"}}), + // ... .id("0-4").build()); 3L, // xlen(streamKey1) Map.of( streamKey1, @@ -1078,7 +1085,7 @@ private static Object[] geospatialCommands(BaseTransaction transaction) { }, // geosearch(geoKey1, "Palermo", byradius(200, km)) new String[] { "Palermo", "Catania" - }, // geosearch(geoKey1, (15,37), bybox(200,200,km)) + }, // geosearch(geoKey1, (15, 37), bybox(400, 400, km)) new Object[] { new Object[] { "Palermo", @@ -1113,9 +1120,8 @@ private static Object[] geospatialCommands(BaseTransaction transaction) { } }, }, // geosearch(geoKey1, (15,37), BYBOX(400,400,km), ASC, COUNT 2) - 2L, // geosearch(geoKey2, geoKey1, (15,37), BYBOX(400,400,km), ASC, COUNT 2) - 2L, // geosearch(geoKey2, geoKey1, (15,37), BYBOX(400,400,km), STOREDIST, ASC, COUNT - // 2) + 2L, // geosearchstore(geoKey2, geoKey1, (15, 37), (400, 400, km), ASC, 2) + 2L, // geosearchstore(geoKey2, geoKey1, (15, 37), (400, 400, km), STOREDIST, ASC, 2) }); } diff --git a/java/integTest/src/test/java/glide/cluster/CommandTests.java b/java/integTest/src/test/java/glide/cluster/CommandTests.java index 89ca922122..989034d10a 100644 --- a/java/integTest/src/test/java/glide/cluster/CommandTests.java +++ b/java/integTest/src/test/java/glide/cluster/CommandTests.java @@ -174,6 +174,17 @@ public void custom_command_info() { } } + @Test + @SneakyThrows + public void custom_command_info_binary() { + ClusterValue data = clusterClient.customCommand(new GlideString[] {gs("info")}).get(); + assertTrue(data.hasMultiData()); + for (Object info : data.getMultiValue().values()) { + assertInstanceOf(GlideString.class, info); + assertTrue(info.toString().contains("# Stats")); + } + } + @Test @SneakyThrows public void custom_command_ping() { @@ -181,6 +192,28 @@ public void custom_command_ping() { assertEquals("PONG", data.getSingleValue()); } + @Test + @SneakyThrows + public void custom_command_ping_binary() { + ClusterValue data = clusterClient.customCommand(new GlideString[] {gs("ping")}).get(); + assertEquals(gs("PONG"), data.getSingleValue()); + } + + @Test + @SneakyThrows + public void custom_command_binary_with_route() { + ClusterValue data = + clusterClient.customCommand(new GlideString[] {gs("info")}, ALL_NODES).get(); + for (Object info : data.getMultiValue().values()) { + assertInstanceOf(GlideString.class, info); + assertTrue(info.toString().contains("# Stats")); + } + + data = clusterClient.customCommand(new GlideString[] {gs("info")}, RANDOM).get(); + assertInstanceOf(GlideString.class, data.getSingleValue()); + assertTrue(data.getSingleValue().toString().contains("# Stats")); + } + @Test @SneakyThrows public void custom_command_del_returns_a_number() { @@ -416,14 +449,14 @@ public void clientGetName_with_multi_node_route() { public void config_reset_stat() { var data = clusterClient.info(InfoOptions.builder().section(STATS).build()).get(); String firstNodeInfo = getFirstEntryFromMultiValue(data); - int value_before = getValueFromInfo(firstNodeInfo, "total_net_input_bytes"); + long value_before = getValueFromInfo(firstNodeInfo, "total_net_input_bytes"); var result = clusterClient.configResetStat().get(); assertEquals(OK, result); data = clusterClient.info(InfoOptions.builder().section(STATS).build()).get(); firstNodeInfo = getFirstEntryFromMultiValue(data); - int value_after = getValueFromInfo(firstNodeInfo, "total_net_input_bytes"); + long value_after = getValueFromInfo(firstNodeInfo, "total_net_input_bytes"); assertTrue(value_after < value_before); } @@ -1018,15 +1051,20 @@ public void flushall() { assertEquals(OK, clusterClient.flushall(ASYNC, route).get()); var replicaRoute = new SlotKeyRoute("key", REPLICA); - // command should fail on a replica, because it is read-only - ExecutionException executionException = - assertThrows(ExecutionException.class, () -> clusterClient.flushall(replicaRoute).get()); - assertInstanceOf(RequestException.class, executionException.getCause()); - assertTrue( - executionException - .getMessage() - .toLowerCase() - .contains("can't write against a read only replica")); + if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + // Since Valkey 8.0.0 flushall can run on replicas + assertEquals(OK, clusterClient.flushall(route).get()); + } else { + // command should fail on a replica, because it is read-only + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> clusterClient.flushall(replicaRoute).get()); + assertInstanceOf(RequestException.class, executionException.getCause()); + assertTrue( + executionException + .getMessage() + .toLowerCase() + .contains("can't write against a read only replica")); + } } // TODO: add a binary version of this test @@ -1607,6 +1645,9 @@ public void fcall_binary_with_keys(String prefix) { @Test public void fcall_readonly_function() { assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0"), "This feature added in version 7"); + assumeTrue( + !SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0"), + "Temporary disabeling this test on valkey 8"); String libName = "fcall_readonly_function"; // intentionally using a REPLICA route @@ -1662,6 +1703,9 @@ public void fcall_readonly_function() { @Test public void fcall_readonly_binary_function() { assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0"), "This feature added in version 7"); + assumeTrue( + !SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0"), + "Temporary disabeling this test on valkey 8"); String libName = "fcall_readonly_function"; // intentionally using a REPLICA route diff --git a/java/integTest/src/test/java/glide/standalone/CommandTests.java b/java/integTest/src/test/java/glide/standalone/CommandTests.java index 0a422e52dc..458c8edff1 100644 --- a/java/integTest/src/test/java/glide/standalone/CommandTests.java +++ b/java/integTest/src/test/java/glide/standalone/CommandTests.java @@ -105,6 +105,14 @@ public void custom_command_info() { assertTrue(((String) data).contains("# Stats")); } + @Test + @SneakyThrows + public void custom_command_info_binary() { + Object data = regularClient.customCommand(new GlideString[] {gs("info")}).get(); + assertInstanceOf(GlideString.class, data); + assertTrue(data.toString().contains("# Stats")); + } + @Test @SneakyThrows public void custom_command_del_returns_a_number() { @@ -278,13 +286,13 @@ public void clientGetName() { @SneakyThrows public void config_reset_stat() { String data = regularClient.info(InfoOptions.builder().section(STATS).build()).get(); - int value_before = getValueFromInfo(data, "total_net_input_bytes"); + long value_before = getValueFromInfo(data, "total_net_input_bytes"); var result = regularClient.configResetStat().get(); assertEquals(OK, result); data = regularClient.info(InfoOptions.builder().section(STATS).build()).get(); - int value_after = getValueFromInfo(data, "total_net_input_bytes"); + long value_after = getValueFromInfo(data, "total_net_input_bytes"); assertTrue(value_after < value_before); } @@ -978,7 +986,9 @@ public void functionStats() { assertEquals(libName, regularClient.functionLoad(code, true).get()); var response = regularClient.functionStats().get(); - checkFunctionStatsResponse(response, new String[0], 1, 1); + for (var nodeResponse : response.values()) { + checkFunctionStatsResponse(nodeResponse, new String[0], 1, 1); + } code = generateLuaLibCode( @@ -988,12 +998,16 @@ public void functionStats() { assertEquals(libName + "_2", regularClient.functionLoad(code, true).get()); response = regularClient.functionStats().get(); - checkFunctionStatsResponse(response, new String[0], 2, 3); + for (var nodeResponse : response.values()) { + checkFunctionStatsResponse(nodeResponse, new String[0], 2, 3); + } assertEquals(OK, regularClient.functionFlush(SYNC).get()); response = regularClient.functionStats().get(); - checkFunctionStatsResponse(response, new String[0], 0, 0); + for (var nodeResponse : response.values()) { + checkFunctionStatsResponse(nodeResponse, new String[0], 0, 0); + } } @Test @@ -1011,7 +1025,9 @@ public void functionStatsBinary() { assertEquals(libName, regularClient.functionLoad(code, true).get()); var response = regularClient.functionStatsBinary().get(); - checkFunctionStatsBinaryResponse(response, new GlideString[0], 1, 1); + for (var nodeResponse : response.values()) { + checkFunctionStatsBinaryResponse(nodeResponse, new GlideString[0], 1, 1); + } code = generateLuaLibCodeBinary( @@ -1025,12 +1041,16 @@ public void functionStatsBinary() { assertEquals(gs(libName.toString() + "_2"), regularClient.functionLoad(code, true).get()); response = regularClient.functionStatsBinary().get(); - checkFunctionStatsBinaryResponse(response, new GlideString[0], 2, 3); + for (var nodeResponse : response.values()) { + checkFunctionStatsBinaryResponse(nodeResponse, new GlideString[0], 2, 3); + } assertEquals(OK, regularClient.functionFlush(SYNC).get()); response = regularClient.functionStatsBinary().get(); - checkFunctionStatsBinaryResponse(response, new GlideString[0], 0, 0); + for (var nodeResponse : response.values()) { + checkFunctionStatsBinaryResponse(nodeResponse, new GlideString[0], 0, 0); + } } @Test @@ -1527,9 +1547,14 @@ public void scan() { assertDeepEquals(new String[] {}, emptyResult[resultCollectionIndex]); // Negative cursor - Object[] negativeResult = regularClient.scan("-1").get(); - assertEquals(initialCursor, negativeResult[resultCursorIndex]); - assertDeepEquals(new String[] {}, negativeResult[resultCollectionIndex]); + if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> regularClient.scan("-1").get()); + } else { + Object[] negativeResult = regularClient.scan("-1").get(); + assertEquals(initialCursor, negativeResult[resultCursorIndex]); + assertDeepEquals(new String[] {}, negativeResult[resultCollectionIndex]); + } // Add keys to the database using mset regularClient.mset(keys).get(); @@ -1581,9 +1606,14 @@ public void scan_binary() { assertDeepEquals(new String[] {}, emptyResult[resultCollectionIndex]); // Negative cursor - Object[] negativeResult = regularClient.scan(gs("-1")).get(); - assertEquals(initialCursor, negativeResult[resultCursorIndex]); - assertDeepEquals(new String[] {}, negativeResult[resultCollectionIndex]); + if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> regularClient.scan(gs("-1")).get()); + } else { + Object[] negativeResult = regularClient.scan(gs("-1")).get(); + assertEquals(initialCursor, negativeResult[resultCursorIndex]); + assertDeepEquals(new String[] {}, negativeResult[resultCollectionIndex]); + } // Add keys to the database using mset regularClient.msetBinary(keys).get(); @@ -1644,9 +1674,16 @@ public void scan_with_options() { assertDeepEquals(new String[] {}, emptyResult[resultCollectionIndex]); // Negative cursor - Object[] negativeResult = regularClient.scan("-1", options).get(); - assertEquals(initialCursor, negativeResult[resultCursorIndex]); - assertDeepEquals(new String[] {}, negativeResult[resultCollectionIndex]); + if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + final ScanOptions finalOptions = options; + ExecutionException executionException = + assertThrows( + ExecutionException.class, () -> regularClient.scan("-1", finalOptions).get()); + } else { + Object[] negativeResult = regularClient.scan("-1", options).get(); + assertEquals(initialCursor, negativeResult[resultCursorIndex]); + assertDeepEquals(new String[] {}, negativeResult[resultCollectionIndex]); + } // scan for strings by match pattern: options = @@ -1726,9 +1763,16 @@ public void scan_binary_with_options() { assertDeepEquals(new String[] {}, emptyResult[resultCollectionIndex]); // Negative cursor - Object[] negativeResult = regularClient.scan(gs("-1"), options).get(); - assertEquals(initialCursor, negativeResult[resultCursorIndex]); - assertDeepEquals(new String[] {}, negativeResult[resultCollectionIndex]); + if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + final ScanOptions finalOptions = options; + ExecutionException executionException = + assertThrows( + ExecutionException.class, () -> regularClient.scan(gs("-1"), finalOptions).get()); + } else { + Object[] negativeResult = regularClient.scan(gs("-1"), options).get(); + assertEquals(initialCursor, negativeResult[resultCursorIndex]); + assertDeepEquals(new String[] {}, negativeResult[resultCollectionIndex]); + } // scan for strings by match pattern: options = diff --git a/logger_core/Cargo.toml b/logger_core/Cargo.toml index be306d4751..f2b7512e14 100644 --- a/logger_core/Cargo.toml +++ b/logger_core/Cargo.toml @@ -13,7 +13,7 @@ test-env-helpers = "0.2.2" [dependencies] tracing = "0.1" -tracing-appender = "0.2.2" +tracing-appender = { version = "0.2.3", default-features = false } once_cell = "1.16.0" file-rotate = "0.7.1" tracing-subscriber = "0.3.17" diff --git a/node/.gitignore b/node/.gitignore index 1cae12ee73..212788384f 100644 --- a/node/.gitignore +++ b/node/.gitignore @@ -8,6 +8,8 @@ lerna-debug.log* .pnpm-debug.log* rust-client/index.* +# Test reports +*.html # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/node/.prettierignore b/node/.prettierignore index 6ced842400..086fea31e1 100644 --- a/node/.prettierignore +++ b/node/.prettierignore @@ -1,6 +1,5 @@ # ignore that dir, because there are a lot of files which we don't manage, e.g. json files in cargo crates rust-client/* -*.md # unignore specific files !rust-client/package.json !rust-client/tsconfig.json diff --git a/node/DEVELOPER.md b/node/DEVELOPER.md index dba47cdef7..ddab936d34 100644 --- a/node/DEVELOPER.md +++ b/node/DEVELOPER.md @@ -62,8 +62,7 @@ Before starting this step, make sure you've installed all software requirments. 1. Clone the repository: ```bash - VERSION=0.1.0 # You can modify this to other released version or set it to "main" to get the unstable branch - git clone --branch ${VERSION} https://github.com/valkey-io/valkey-glide.git + git clone https://github.com/valkey-io/valkey-glide.git cd valkey-glide ``` 2. Initialize git submodule: diff --git a/node/THIRD_PARTY_LICENSES_NODE b/node/THIRD_PARTY_LICENSES_NODE index 8f750e1968..fd31743b0c 100644 --- a/node/THIRD_PARTY_LICENSES_NODE +++ b/node/THIRD_PARTY_LICENSES_NODE @@ -683,10 +683,23 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ahash:0.8.11 +Package: adler2:2.0.0 The following copyrights and licenses were found in the source code of this package: +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + -- + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -912,59 +925,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: aho-corasick:1.1.3 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -- - -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to - ----- - -Package: allocator-api2:0.2.18 +Package: ahash:0.8.11 The following copyrights and licenses were found in the source code of this package: @@ -1193,7 +1154,59 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: android-tzdata:0.1.1 +Package: aho-corasick:1.1.3 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -- + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +---- + +Package: allocator-api2:0.2.18 The following copyrights and licenses were found in the source code of this package: @@ -1422,7 +1435,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: android_system_properties:0.1.5 +Package: android-tzdata:0.1.1 The following copyrights and licenses were found in the source code of this package: @@ -1651,7 +1664,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: arc-swap:1.7.1 +Package: android_system_properties:0.1.5 The following copyrights and licenses were found in the source code of this package: @@ -1880,7 +1893,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: arcstr:1.2.0 +Package: arc-swap:1.7.1 The following copyrights and licenses were found in the source code of this package: @@ -2107,29 +2120,9 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. - -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. - ---- -Package: async-trait:0.1.81 +Package: arcstr:1.2.0 The following copyrights and licenses were found in the source code of this package: @@ -2356,9 +2349,29 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -- + +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + ---- -Package: backoff:0.4.0 +Package: async-trait:0.1.81 The following copyrights and licenses were found in the source code of this package: @@ -2587,7 +2600,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: backtrace:0.3.73 +Package: backoff:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -2816,7 +2829,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: base64:0.22.1 +Package: backtrace:0.3.73 The following copyrights and licenses were found in the source code of this package: @@ -3045,7 +3058,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bitflags:2.6.0 +Package: base64:0.22.1 The following copyrights and licenses were found in the source code of this package: @@ -3274,7 +3287,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bumpalo:3.16.0 +Package: bitflags:2.6.0 The following copyrights and licenses were found in the source code of this package: @@ -3503,84 +3516,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: byteorder:1.5.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -- - -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to - ----- - -Package: bytes:1.6.1 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: cfg-if:1.0.0 +Package: bumpalo:3.16.0 The following copyrights and licenses were found in the source code of this package: @@ -3809,7 +3745,84 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: chrono:0.4.38 +Package: byteorder:1.5.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -- + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +---- + +Package: bytes:1.7.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: cfg-if:1.0.0 The following copyrights and licenses were found in the source code of this package: @@ -4038,57 +4051,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: combine:4.6.7 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: convert_case:0.6.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: core-foundation:0.9.4 +Package: chrono:0.4.38 The following copyrights and licenses were found in the source code of this package: @@ -4317,7 +4280,57 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: core-foundation-sys:0.8.6 +Package: combine:4.6.7 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: convert_case:0.6.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: core-foundation:0.9.4 The following copyrights and licenses were found in the source code of this package: @@ -4546,32 +4559,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crc16:0.4.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: crc32fast:1.4.2 +Package: core-foundation-sys:0.8.7 The following copyrights and licenses were found in the source code of this package: @@ -4800,7 +4788,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crossbeam-channel:0.5.13 +Package: crc16:0.4.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: crc32fast:1.4.2 The following copyrights and licenses were found in the source code of this package: @@ -5029,7 +5042,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crossbeam-utils:0.8.20 +Package: crossbeam-channel:0.5.13 The following copyrights and licenses were found in the source code of this package: @@ -5258,7 +5271,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ctor:0.2.8 +Package: crossbeam-utils:0.8.20 The following copyrights and licenses were found in the source code of this package: @@ -5487,7 +5500,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: deranged:0.3.11 +Package: ctor:0.2.8 The following copyrights and licenses were found in the source code of this package: @@ -5716,7 +5729,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: derivative:2.2.0 +Package: dashmap:6.0.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: deranged:0.3.11 The following copyrights and licenses were found in the source code of this package: @@ -5945,7 +5983,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: directories:4.0.1 +Package: derivative:2.2.0 The following copyrights and licenses were found in the source code of this package: @@ -6174,7 +6212,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: dirs-sys:0.3.7 +Package: directories:4.0.1 The following copyrights and licenses were found in the source code of this package: @@ -6403,7 +6441,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: dispose:0.5.0 +Package: dirs-sys:0.3.7 The following copyrights and licenses were found in the source code of this package: @@ -6632,7 +6670,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: dispose-derive:0.4.0 +Package: dispose:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -6861,7 +6899,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: fast-math:0.1.1 +Package: dispose-derive:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -7090,32 +7128,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: file-rotate:0.7.6 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: flate2:1.0.30 +Package: fast-math:0.1.1 The following copyrights and licenses were found in the source code of this package: @@ -7344,7 +7357,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: form_urlencoded:1.2.1 +Package: file-rotate:0.7.6 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: flate2:1.0.33 The following copyrights and licenses were found in the source code of this package: @@ -7573,7 +7611,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures:0.3.30 +Package: form_urlencoded:1.2.1 The following copyrights and licenses were found in the source code of this package: @@ -7802,7 +7840,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-channel:0.3.30 +Package: futures:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -8031,7 +8069,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-core:0.3.30 +Package: futures-channel:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -8260,7 +8298,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-executor:0.3.30 +Package: futures-core:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -8489,7 +8527,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-intrusive:0.5.0 +Package: futures-executor:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -8718,7 +8756,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-io:0.3.30 +Package: futures-intrusive:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -8947,7 +8985,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-macro:0.3.30 +Package: futures-io:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9176,7 +9214,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-sink:0.3.30 +Package: futures-macro:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9405,7 +9443,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-task:0.3.30 +Package: futures-sink:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9634,7 +9672,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-util:0.3.30 +Package: futures-task:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9863,7 +9901,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: getrandom:0.2.15 +Package: futures-util:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -10092,7 +10130,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: gimli:0.29.0 +Package: getrandom:0.2.15 The following copyrights and licenses were found in the source code of this package: @@ -10321,7 +10359,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: glide-core:0.1.0 +Package: gimli:0.29.0 The following copyrights and licenses were found in the source code of this package: @@ -10527,9 +10565,30 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. + -- + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ---- -Package: hashbrown:0.14.5 +Package: glide-core:0.1.0 The following copyrights and licenses were found in the source code of this package: @@ -10735,30 +10794,9 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- -Package: heck:0.5.0 +Package: hashbrown:0.14.5 The following copyrights and licenses were found in the source code of this package: @@ -10987,7 +11025,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: hermit-abi:0.3.9 +Package: heck:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -11216,7 +11254,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: iana-time-zone:0.1.60 +Package: hermit-abi:0.3.9 The following copyrights and licenses were found in the source code of this package: @@ -11445,7 +11483,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: iana-time-zone-haiku:0.1.2 +Package: iana-time-zone:0.1.60 The following copyrights and licenses were found in the source code of this package: @@ -11674,7 +11712,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: idna:0.5.0 +Package: iana-time-zone-haiku:0.1.2 The following copyrights and licenses were found in the source code of this package: @@ -11903,7 +11941,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ieee754:0.2.6 +Package: idna:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -12132,63 +12170,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: instant:0.1.13 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: integer-encoding:4.0.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: itoa:1.0.11 +Package: ieee754:0.2.6 The following copyrights and licenses were found in the source code of this package: @@ -12417,7 +12399,63 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: js-sys:0.3.69 +Package: instant:0.1.13 + +The following copyrights and licenses were found in the source code of this package: + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + +Package: integer-encoding:4.0.2 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: itoa:1.0.11 The following copyrights and licenses were found in the source code of this package: @@ -12646,7 +12684,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: lazy_static:1.5.0 +Package: js-sys:0.3.70 The following copyrights and licenses were found in the source code of this package: @@ -12875,7 +12913,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: libc:0.2.155 +Package: lazy_static:1.5.0 The following copyrights and licenses were found in the source code of this package: @@ -13104,50 +13142,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: libloading:0.8.4 - -The following copyrights and licenses were found in the source code of this package: - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - ----- - -Package: libredox:0.1.3 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: lock_api:0.4.12 +Package: libc:0.2.158 The following copyrights and licenses were found in the source code of this package: @@ -13376,213 +13371,27 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: log:0.4.22 +Package: libloading:0.8.5 The following copyrights and licenses were found in the source code of this package: - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. - http://www.apache.org/licenses/LICENSE-2.0 +---- - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Package: libredox:0.1.3 - -- +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -13605,7 +13414,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: logger_core:0.1.0 +Package: lock_api:0.4.12 The following copyrights and licenses were found in the source code of this package: @@ -13811,11 +13620,7 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. ----- - -Package: memchr:2.7.4 - -The following copyrights and licenses were found in the source code of this package: + -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -13836,36 +13641,9 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to - ---- -Package: miniz_oxide:0.7.4 +Package: log:0.4.22 The following copyrights and licenses were found in the source code of this package: @@ -14092,54 +13870,217 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- +---- -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. +Package: logger_core:0.1.0 -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: +The following copyrights and licenses were found in the source code of this package: -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -3. This notice may not be removed or altered from any source 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. -Package: mio:0.8.11 + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -The following copyrights and licenses were found in the source code of this package: + "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. -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + "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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. ---- -Package: nanoid:0.4.0 +Package: memchr:2.7.4 The following copyrights and licenses were found in the source code of this package: @@ -14162,159 +14103,36 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----- - -Package: napi:2.16.8 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: napi-build:2.1.3 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: napi-derive:2.16.9 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: napi-derive-backend:1.0.71 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: napi-sys:2.4.0 + -- -The following copyrights and licenses were found in the source code of this package: +This is free and unencumbered software released into the public domain. -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: nu-ansi-term:0.46.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +For more information, please refer to ---- -Package: num-bigint:0.4.6 +Package: miniz_oxide:0.7.4 The following copyrights and licenses were found in the source code of this package: @@ -14541,9 +14359,29 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -- + +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + ---- -Package: num-conv:0.1.0 +Package: miniz_oxide:0.8.0 The following copyrights and licenses were found in the source code of this package: @@ -14770,215 +14608,31 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----- - -Package: num-integer:0.1.46 - -The following copyrights and licenses were found in the source code of this package: - - 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 +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. - APPENDIX: How to apply the Apache License to your work. +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. - Copyright [yyyy] [name of copyright owner] +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +3. This notice may not be removed or altered from any source distribution. - http://www.apache.org/licenses/LICENSE-2.0 +---- - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Package: mio:1.0.2 - -- +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -15001,901 +14655,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: num-traits:0.2.19 +Package: nanoid:0.4.0 The following copyrights and licenses were found in the source code of this package: - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: num_cpus:1.16.0 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: object:0.36.1 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: once_cell:1.19.0 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -15917,213 +14680,109 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: openssl-probe:0.1.5 +Package: napi:2.16.9 The following copyrights and licenses were found in the source code of this package: - 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. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - "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." +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - "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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - 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. +Package: napi-build:2.1.3 - 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: +The following copyrights and licenses were found in the source code of this package: - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - (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 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - (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. +Package: napi-derive:2.16.11 - 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. +The following copyrights and licenses were found in the source code of this package: - 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. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - 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. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - 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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - 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 +Package: napi-derive-backend:1.0.73 - APPENDIX: How to apply the Apache License to your work. +The following copyrights and licenses were found in the source code of this package: - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - Copyright [yyyy] [name of copyright owner] +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - http://www.apache.org/licenses/LICENSE-2.0 +---- - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Package: napi-sys:2.4.0 - -- +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -16146,7 +14805,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: overload:0.1.1 +Package: nu-ansi-term:0.46.0 The following copyrights and licenses were found in the source code of this package: @@ -16171,7 +14830,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: parking_lot:0.12.3 +Package: num-bigint:0.4.6 The following copyrights and licenses were found in the source code of this package: @@ -16400,7 +15059,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: parking_lot_core:0.9.10 +Package: num-conv:0.1.0 The following copyrights and licenses were found in the source code of this package: @@ -16629,7 +15288,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: percent-encoding:2.3.1 +Package: num-integer:0.1.46 The following copyrights and licenses were found in the source code of this package: @@ -16858,7 +15517,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project:1.1.5 +Package: num-traits:0.2.19 The following copyrights and licenses were found in the source code of this package: @@ -17087,7 +15746,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-internal:1.1.5 +Package: num_cpus:1.16.0 The following copyrights and licenses were found in the source code of this package: @@ -17316,7 +15975,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-lite:0.2.14 +Package: object:0.36.3 The following copyrights and licenses were found in the source code of this package: @@ -17545,7 +16204,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-utils:0.1.0 +Package: once_cell:1.19.0 The following copyrights and licenses were found in the source code of this package: @@ -17774,7 +16433,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: powerfmt:0.2.0 +Package: openssl-probe:0.1.5 The following copyrights and licenses were found in the source code of this package: @@ -18003,7 +16662,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ppv-lite86:0.2.17 +Package: overload:0.1.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: parking_lot:0.12.3 The following copyrights and licenses were found in the source code of this package: @@ -18232,7 +16916,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro-error:1.0.4 +Package: parking_lot_core:0.9.10 The following copyrights and licenses were found in the source code of this package: @@ -18461,7 +17145,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro-error-attr:1.0.4 +Package: percent-encoding:2.3.1 The following copyrights and licenses were found in the source code of this package: @@ -18690,7 +17374,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro2:1.0.86 +Package: pin-project:1.1.5 The following copyrights and licenses were found in the source code of this package: @@ -18919,57 +17603,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: protobuf:3.5.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: protobuf-support:3.5.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: quote:1.0.36 +Package: pin-project-internal:1.1.5 The following copyrights and licenses were found in the source code of this package: @@ -19198,7 +17832,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand:0.8.5 +Package: pin-project-lite:0.2.14 The following copyrights and licenses were found in the source code of this package: @@ -19427,7 +18061,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand_chacha:0.3.1 +Package: pin-utils:0.1.0 The following copyrights and licenses were found in the source code of this package: @@ -19656,7 +18290,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand_core:0.6.4 +Package: powerfmt:0.2.0 The following copyrights and licenses were found in the source code of this package: @@ -19885,88 +18519,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: redis:0.25.2 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: redox_syscall:0.5.3 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: redox_users:0.4.5 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: regex:1.10.5 +Package: ppv-lite86:0.2.20 The following copyrights and licenses were found in the source code of this package: @@ -20195,7 +18748,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: regex-automata:0.4.7 +Package: proc-macro-error:1.0.4 The following copyrights and licenses were found in the source code of this package: @@ -20424,7 +18977,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: regex-syntax:0.8.4 +Package: proc-macro-error-attr:1.0.4 The following copyrights and licenses were found in the source code of this package: @@ -20653,7 +19206,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustc-demangle:0.1.24 +Package: proc-macro2:1.0.86 The following copyrights and licenses were found in the source code of this package: @@ -20882,7 +19435,57 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls:0.22.4 +Package: protobuf:3.5.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: protobuf-support:3.5.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: quote:1.0.37 The following copyrights and licenses were found in the source code of this package: @@ -21090,20 +19693,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - - -- - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -21125,7 +19714,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-native-certs:0.7.1 +Package: rand:0.8.5 The following copyrights and licenses were found in the source code of this package: @@ -21333,20 +19922,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - - -- - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -21368,7 +19943,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pemfile:2.1.2 +Package: rand_chacha:0.3.1 The following copyrights and licenses were found in the source code of this package: @@ -21576,20 +20151,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - - -- - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -21611,7 +20172,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pki-types:1.7.0 +Package: rand_core:0.6.4 The following copyrights and licenses were found in the source code of this package: @@ -21840,231 +20401,65 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-webpki:0.102.5 - -The following copyrights and licenses were found in the source code of this package: - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - ----- - -Package: rustversion:1.0.17 +Package: redis:0.25.2 The following copyrights and licenses were found in the source code of this package: - 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. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: - 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. +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. - 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. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. - 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. +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. - 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. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - 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 +Package: redox_syscall:0.5.3 - APPENDIX: How to apply the Apache License to your work. +The following copyrights and licenses were found in the source code of this package: - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - Copyright [yyyy] [name of copyright owner] +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - http://www.apache.org/licenses/LICENSE-2.0 +---- - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Package: redox_users:0.4.6 - -- +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -22087,7 +20482,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ryu:1.0.18 +Package: regex:1.10.6 The following copyrights and licenses were found in the source code of this package: @@ -22295,36 +20690,235 @@ The following copyrights and licenses were found in the source code of this pack -- -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: schannel:0.1.23 +Package: regex-automata:0.4.7 The following copyrights and licenses were found in the source code of this package: + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + -- + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -22346,7 +20940,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: scopeguard:1.2.0 +Package: regex-syntax:0.8.4 The following copyrights and licenses were found in the source code of this package: @@ -22575,7 +21169,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework:2.11.1 +Package: rustc-demangle:0.1.24 The following copyrights and licenses were found in the source code of this package: @@ -22804,7 +21398,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework-sys:2.11.1 +Package: rustls:0.22.4 The following copyrights and licenses were found in the source code of this package: @@ -23012,6 +21606,20 @@ The following copyrights and licenses were found in the source code of this pack -- +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + -- + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -23033,7 +21641,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: semver:1.0.23 +Package: rustls-native-certs:0.7.2 The following copyrights and licenses were found in the source code of this package: @@ -23241,6 +21849,20 @@ The following copyrights and licenses were found in the source code of this pack -- +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + -- + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -23262,7 +21884,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde:1.0.204 +Package: rustls-pemfile:2.1.3 The following copyrights and licenses were found in the source code of this package: @@ -23470,6 +22092,20 @@ The following copyrights and licenses were found in the source code of this pack -- +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + -- + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -23491,7 +22127,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde_derive:1.0.204 +Package: rustls-pki-types:1.8.0 The following copyrights and licenses were found in the source code of this package: @@ -23720,65 +22356,231 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: sha1_smol:1.0.0 +Package: rustls-webpki:0.102.6 The following copyrights and licenses were found in the source code of this package: -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. +---- -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. +Package: rustversion:1.0.17 -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +The following copyrights and licenses were found in the source code of this package: ----- + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Package: sharded-slab:0.1.7 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -The following copyrights and licenses were found in the source code of this package: + 1. Definitions. -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + "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. -Package: slab:0.4.9 + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. -The following copyrights and licenses were found in the source code of this package: + "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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -23801,7 +22603,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: smallvec:1.13.2 +Package: ryu:1.0.18 The following copyrights and licenses were found in the source code of this package: @@ -24009,6 +22811,36 @@ The following copyrights and licenses were found in the source code of this pack -- +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +---- + +Package: schannel:0.1.23 + +The following copyrights and licenses were found in the source code of this package: + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -24030,7 +22862,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: socket2:0.5.7 +Package: scopeguard:1.2.0 The following copyrights and licenses were found in the source code of this package: @@ -24259,113 +23091,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: spin:0.9.8 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: strum:0.26.3 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: strum_macros:0.26.4 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: subtle:2.6.1 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: syn:1.0.109 +Package: security-framework:2.11.1 The following copyrights and licenses were found in the source code of this package: @@ -24594,7 +23320,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.71 +Package: security-framework-sys:2.11.1 The following copyrights and licenses were found in the source code of this package: @@ -24823,7 +23549,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: thiserror:1.0.62 +Package: semver:1.0.23 The following copyrights and licenses were found in the source code of this package: @@ -25052,7 +23778,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: thiserror-impl:1.0.62 +Package: serde:1.0.209 The following copyrights and licenses were found in the source code of this package: @@ -25281,7 +24007,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: thread_local:1.1.8 +Package: serde_derive:1.0.209 The following copyrights and licenses were found in the source code of this package: @@ -25510,7 +24236,88 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: tikv-jemalloc-sys:0.5.4+5.3.0-patched +Package: sha1_smol:1.0.1 + +The following copyrights and licenses were found in the source code of this package: + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + +Package: sharded-slab:0.1.7 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: slab:0.4.9 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: smallvec:1.13.2 The following copyrights and licenses were found in the source code of this package: @@ -25739,7 +24546,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: tikv-jemallocator:0.5.4 +Package: socket2:0.5.7 The following copyrights and licenses were found in the source code of this package: @@ -25968,214 +24775,60 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: time:0.3.36 +Package: spin:0.9.8 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: strum:0.26.3 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: strum_macros:0.26.4 The following copyrights and licenses were found in the source code of this package: - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -26197,7 +24850,38 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: time-core:0.1.2 +Package: subtle:2.6.1 + +The following copyrights and licenses were found in the source code of this package: + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + +Package: syn:1.0.109 The following copyrights and licenses were found in the source code of this package: @@ -26426,7 +25110,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: time-macros:0.2.18 +Package: syn:2.0.76 The following copyrights and licenses were found in the source code of this package: @@ -26655,7 +25339,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: tinyvec:1.8.0 +Package: thiserror:1.0.63 The following copyrights and licenses were found in the source code of this package: @@ -26882,29 +25566,9 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. - -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. - ---- -Package: tinyvec_macros:0.1.1 +Package: thiserror-impl:1.0.63 The following copyrights and licenses were found in the source code of this package: @@ -27131,104 +25795,9 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. - -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. - ---- -Package: tokio:1.38.1 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tokio-macros:2.3.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tokio-retry:0.3.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tokio-rustls:0.25.0 +Package: thread_local:1.1.8 The following copyrights and licenses were found in the source code of this package: @@ -27457,182 +26026,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: tokio-util:0.7.11 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing:0.1.40 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-appender:0.2.3 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-attributes:0.1.27 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-core:0.1.32 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-log:0.2.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-subscriber:0.3.18 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: unicode-bidi:0.3.15 +Package: tikv-jemalloc-sys:0.5.4+5.3.0-patched The following copyrights and licenses were found in the source code of this package: @@ -27861,7 +26255,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: unicode-ident:1.0.12 +Package: tikv-jemallocator:0.5.4 The following copyrights and licenses were found in the source code of this package: @@ -28088,70 +26482,9 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE - -Unicode Data Files include all data files under the directories -http://www.unicode.org/Public/, http://www.unicode.org/reports/, -http://www.unicode.org/cldr/data/, http://source.icu- -project.org/repos/icu/, and -http://www.unicode.org/utility/trac/browser/. - -Unicode Data Files do not include PDF online code charts under the -directory http://www.unicode.org/Public/. - -Software includes any source code published in the Unicode Standard or -under the directories http://www.unicode.org/Public/, -http://www.unicode.org/reports/, http://www.unicode.org/cldr/data/, -http://source.icu-project.org/repos/icu/, and -http://www.unicode.org/utility/trac/browser/. - -NOTICE TO USER: Carefully read the following legal agreement. BY -DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA -FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY -ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF -THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, -DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. - -COPYRIGHT AND PERMISSION NOTICE - -Copyright © 1991-2016 Unicode, Inc. All rights reserved. Distributed -under the Terms of Use in http://www.unicode.org/copyright.html. - -Permission is hereby granted, free of charge, to any person obtaining a -copy of the Unicode data files and any associated documentation (the -"Data Files") or Unicode software and any associated documentation (the -"Software") to deal in the Data Files or Software without restriction, -including without limitation the rights to use, copy, modify, merge, -publish, distribute, and/or sell copies of the Data Files or Software, -and to permit persons to whom the Data Files or Software are furnished -to do so, provided that either - -(a) this copyright and permission notice appear with all copies of the -Data Files or Software, or - -(b) this copyright and permission notice appear in associated -Documentation. - -THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR -ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER -RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF -CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. - -Except as contained in this notice, the name of a copyright holder shall -not be used in advertising or otherwise to promote the sale, use or -other dealings in these Data Files or Software without prior written -authorization of the copyright holder. - ---- -Package: unicode-normalization:0.1.23 +Package: time:0.3.36 The following copyrights and licenses were found in the source code of this package: @@ -28380,7 +26713,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: unicode-segmentation:1.11.0 +Package: time-core:0.1.2 The following copyrights and licenses were found in the source code of this package: @@ -28609,25 +26942,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: untrusted:0.9.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - ----- - -Package: url:2.5.0 +Package: time-macros:0.2.18 The following copyrights and licenses were found in the source code of this package: @@ -28856,32 +27171,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: valuable:0.1.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: wasi:0.11.0+wasi-snapshot-preview1 +Package: tinyvec:1.8.0 The following copyrights and licenses were found in the source code of this package: @@ -29089,6 +27379,51 @@ The following copyrights and licenses were found in the source code of this pack -- +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -- + +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + +---- + +Package: tinyvec_macros:0.1.1 + +The following copyrights and licenses were found in the source code of this package: + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -29291,24 +27626,53 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. ---- LLVM Exceptions to the Apache 2.0 License ---- + -- -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -- +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + +---- + +Package: tokio:1.39.3 + +The following copyrights and licenses were found in the source code of this package: + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -29330,7 +27694,57 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen:0.2.92 +Package: tokio-macros:2.4.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tokio-retry:0.3.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tokio-rustls:0.25.0 The following copyrights and licenses were found in the source code of this package: @@ -29559,7 +27973,182 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-backend:0.2.92 +Package: tokio-util:0.7.11 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing:0.1.40 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing-appender:0.2.3 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing-attributes:0.1.27 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing-core:0.1.32 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing-log:0.2.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing-subscriber:0.3.18 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: unicode-bidi:0.3.15 The following copyrights and licenses were found in the source code of this package: @@ -29788,7 +28377,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro:0.2.92 +Package: unicode-ident:1.0.12 The following copyrights and licenses were found in the source code of this package: @@ -30015,9 +28604,70 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -- + +UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE + +Unicode Data Files include all data files under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu- +project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +Unicode Data Files do not include PDF online code charts under the +directory http://www.unicode.org/Public/. + +Software includes any source code published in the Unicode Standard or +under the directories http://www.unicode.org/Public/, +http://www.unicode.org/reports/, http://www.unicode.org/cldr/data/, +http://source.icu-project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA +FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY +ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF +THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, +DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2016 Unicode, Inc. All rights reserved. Distributed +under the Terms of Use in http://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of the Unicode data files and any associated documentation (the +"Data Files") or Unicode software and any associated documentation (the +"Software") to deal in the Data Files or Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, and/or sell copies of the Data Files or Software, +and to permit persons to whom the Data Files or Software are furnished +to do so, provided that either + +(a) this copyright and permission notice appear with all copies of the +Data Files or Software, or + +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR +ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or +other dealings in these Data Files or Software without prior written +authorization of the copyright holder. + ---- -Package: wasm-bindgen-macro-support:0.2.92 +Package: unicode-normalization:0.1.23 The following copyrights and licenses were found in the source code of this package: @@ -30246,7 +28896,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-shared:0.2.92 +Package: unicode-segmentation:1.11.0 The following copyrights and licenses were found in the source code of this package: @@ -30475,7 +29125,25 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: winapi:0.3.9 +Package: untrusted:0.9.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +---- + +Package: url:2.5.0 The following copyrights and licenses were found in the source code of this package: @@ -30704,7 +29372,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: winapi-i686-pc-windows-gnu:0.4.0 +Package: valuable:0.1.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: wasi:0.11.0+wasi-snapshot-preview1 The following copyrights and licenses were found in the source code of this package: @@ -30912,31 +29605,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: winapi-x86_64-pc-windows-gnu:0.4.0 - -The following copyrights and licenses were found in the source code of this package: - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -31139,234 +29807,21 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: windows-core:0.52.0 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +--- LLVM Exceptions to the Apache 2.0 License ---- - http://www.apache.org/licenses/LICENSE-2.0 +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. -- @@ -31391,7 +29846,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-sys:0.48.0 +Package: wasm-bindgen:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -31620,7 +30075,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-sys:0.52.0 +Package: wasm-bindgen-backend:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -31849,7 +30304,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.48.5 +Package: wasm-bindgen-macro:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -32078,7 +30533,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.52.6 +Package: wasm-bindgen-macro-support:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -32307,7 +30762,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.48.5 +Package: wasm-bindgen-shared:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -32536,7 +30991,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.52.6 +Package: winapi:0.3.9 The following copyrights and licenses were found in the source code of this package: @@ -32765,7 +31220,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.48.5 +Package: winapi-i686-pc-windows-gnu:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -32994,7 +31449,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.52.6 +Package: winapi-x86_64-pc-windows-gnu:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -33223,7 +31678,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.48.5 +Package: windows-core:0.52.0 The following copyrights and licenses were found in the source code of this package: @@ -33452,7 +31907,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.52.6 +Package: windows-sys:0.52.0 The following copyrights and licenses were found in the source code of this package: @@ -33681,7 +32136,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnullvm:0.52.6 +Package: windows-targets:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -33910,7 +32365,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.48.5 +Package: windows_aarch64_gnullvm:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -34139,7 +32594,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.52.6 +Package: windows_aarch64_msvc:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -34368,7 +32823,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.48.5 +Package: windows_i686_gnu:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -34597,7 +33052,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.52.6 +Package: windows_i686_gnullvm:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -34826,7 +33281,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnullvm:0.48.5 +Package: windows_i686_msvc:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -35055,7 +33510,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnullvm:0.52.6 +Package: windows_x86_64_gnu:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -35284,7 +33739,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_msvc:0.48.5 +Package: windows_x86_64_gnullvm:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -37559,7 +36014,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: eslint-module-utils:2.8.1 +Package: eslint-module-utils:2.8.2 The following copyrights and licenses were found in the source code of this package: @@ -38154,7 +36609,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: is-core-module:2.14.0 +Package: is-core-module:2.15.1 The following copyrights and licenses were found in the source code of this package: @@ -39055,7 +37510,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: protobufjs:7.3.2 +Package: protobufjs:7.4.0 The following copyrights and licenses were found in the source code of this package: @@ -39554,7 +38009,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: undici-types:5.26.5 +Package: undici-types:6.19.8 The following copyrights and licenses were found in the source code of this package: @@ -39964,7 +38419,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: @types:node:20.14.11 +Package: @types:node:22.5.0 The following copyrights and licenses were found in the source code of this package: diff --git a/node/jest.config.js b/node/jest.config.js index f58b30d383..1779b005b4 100644 --- a/node/jest.config.js +++ b/node/jest.config.js @@ -13,5 +13,17 @@ module.exports = { "cjs", "mjs", ], - testTimeout: 20000, + testTimeout: 600000, + reporters: [ + "default", + [ + "./node_modules/jest-html-reporter", + { + includeFailureMsg: true, + includeSuiteFailure: true, + executionTimeWarningThreshold: 60, + sort: "status", + }, + ], + ], }; diff --git a/node/npm/glide/index.ts b/node/npm/glide/index.ts index cfabd89a03..65840cf7a8 100644 --- a/node/npm/glide/index.ts +++ b/node/npm/glide/index.ts @@ -9,6 +9,7 @@ import { arch, platform } from "process"; let globalObject = global as unknown; +/* eslint-disable @typescript-eslint/no-require-imports */ function loadNativeBinding() { let nativeBinding = null; switch (platform) { @@ -74,39 +75,104 @@ function loadNativeBinding() { function initialize() { const nativeBinding = loadNativeBinding(); const { + AggregationType, + BaseScanOptions, + BitEncoding, + BitFieldGet, + BitFieldIncrBy, + BitFieldOffset, + BitFieldOverflow, + BitFieldSet, + BitFieldSubCommands, + BitOffset, + BitOffsetMultiplier, + BitOffsetOptions, + BitOverflowControl, + BitmapIndexType, + BitwiseOperation, + ConditionalChange, + Decoder, + DecoderOption, + GeoAddOptions, + CoordOrigin, + MemberOrigin, + SearchOrigin, + GeoBoxShape, + GeoCircleShape, + GeoSearchShape, + GeoSearchResultOptions, + GeoSearchStoreResultOptions, + SortOrder, + GeoUnit, + GeospatialData, GlideClient, GlideClusterClient, GlideClientConfiguration, + GlideString, + FunctionListOptions, + FunctionListResponse, + FunctionStatsSingleResponse, + FunctionStatsFullResponse, + FunctionRestorePolicy, SlotIdTypes, SlotKeyTypes, + TimeUnit, RouteByAddress, + RouteOption, Routes, + RestoreOptions, SingleNodeRoute, PeriodicChecksManualInterval, PeriodicChecks, Logger, + Limit, + LolwutOptions, + LPosOptions, + ListDirection, ExpireOptions, + FlushMode, InfoOptions, InsertPosition, SetOptions, - ZaddOptions, - ScoreBoundry, + ZAddOptions, + InfBoundary, + KeyWeight, + Boundary, + UpdateOptions, + ProtocolVersion, RangeByIndex, RangeByScore, RangeByLex, + ReadFrom, + RedisCredentials, + SortClusterOptions, + SortOptions, SortedSetRange, + StreamGroupOptions, StreamTrimOptions, StreamAddOptions, + StreamReadGroupOptions, StreamReadOptions, + StreamClaimOptions, + StreamPendingOptions, ScriptOptions, ClosingError, + ConfigurationError, ExecAbortError, RedisError, + ReturnType, + StreamEntries, + ReturnTypeXinfoStream, RequestError, TimeoutError, ConnectionError, ClusterTransaction, Transaction, + PubSubMsg, + ScoreFilter, + SignedEncoding, + UnsignedEncoding, + UpdateByScore, createLeakedArray, createLeakedAttribute, createLeakedBigint, @@ -117,39 +183,104 @@ function initialize() { } = nativeBinding; module.exports = { + AggregationType, + BaseScanOptions, + BitEncoding, + BitFieldGet, + BitFieldIncrBy, + BitFieldOffset, + BitFieldOverflow, + BitFieldSet, + BitFieldSubCommands, + BitOffset, + BitOffsetMultiplier, + BitOffsetOptions, + BitOverflowControl, + BitmapIndexType, + BitwiseOperation, + ConditionalChange, + Decoder, + DecoderOption, + GeoAddOptions, + GlideString, + CoordOrigin, + MemberOrigin, + SearchOrigin, + GeoBoxShape, + GeoCircleShape, + GeoSearchShape, + GeoSearchResultOptions, + GeoSearchStoreResultOptions, + SortOrder, + GeoUnit, + GeospatialData, GlideClient, GlideClusterClient, GlideClientConfiguration, + FunctionListOptions, + FunctionListResponse, + FunctionStatsSingleResponse, + FunctionStatsFullResponse, + FunctionRestorePolicy, SlotIdTypes, SlotKeyTypes, + StreamEntries, + TimeUnit, + ReturnTypeXinfoStream, RouteByAddress, + RouteOption, Routes, + RestoreOptions, SingleNodeRoute, PeriodicChecksManualInterval, PeriodicChecks, Logger, + LolwutOptions, + Limit, + LPosOptions, + ListDirection, ExpireOptions, + FlushMode, InfoOptions, InsertPosition, SetOptions, - ZaddOptions, - ScoreBoundry, + ZAddOptions, + InfBoundary, + KeyWeight, + Boundary, + UpdateOptions, + ProtocolVersion, RangeByIndex, RangeByScore, RangeByLex, + ReadFrom, + RedisCredentials, + SortClusterOptions, + SortOptions, SortedSetRange, + StreamGroupOptions, StreamTrimOptions, StreamAddOptions, + StreamClaimOptions, + StreamReadGroupOptions, StreamReadOptions, + StreamPendingOptions, ScriptOptions, ClosingError, + ConfigurationError, ExecAbortError, RedisError, + ReturnType, RequestError, TimeoutError, ConnectionError, ClusterTransaction, Transaction, + PubSubMsg, + ScoreFilter, + SignedEncoding, + UnsignedEncoding, + UpdateByScore, createLeakedArray, createLeakedAttribute, createLeakedBigint, diff --git a/node/package.json b/node/package.json index 21a02e70b9..2f7a2e180b 100644 --- a/node/package.json +++ b/node/package.json @@ -55,12 +55,14 @@ "eslint-plugin-tsdoc": "^0.2.17", "find-free-port": "^2.0.0", "jest": "^28.1.3", + "jest-html-reporter": "^3.10.2", "protobufjs-cli": "^1.1.1", "redis-server": "^1.2.2", "replace": "^1.2.2", "ts-jest": "^28.0.8", "typescript": "^4.9.5", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "semver": "^7.6.3" }, "author": "Amazon Web Services", "license": "Apache-2.0", diff --git a/node/rust-client/src/lib.rs b/node/rust-client/src/lib.rs index b5ea8f39c2..896478f76a 100644 --- a/node/rust-client/src/lib.rs +++ b/node/rust-client/src/lib.rs @@ -76,7 +76,7 @@ impl AsyncClient { }) } - #[napi(ts_return_type = "Promise")] + #[napi(ts_return_type = "Promise")] #[allow(dead_code)] pub fn get(&self, env: Env, key: String) -> Result { let (deferred, promise) = env.create_deferred()?; @@ -93,7 +93,7 @@ impl AsyncClient { Ok(promise) } - #[napi(ts_return_type = "Promise")] + #[napi(ts_return_type = "Promise")] #[allow(dead_code)] pub fn set(&self, env: Env, key: String, value: String) -> Result { let (deferred, promise) = env.create_deferred()?; @@ -160,19 +160,37 @@ pub fn init(level: Option, file_name: Option<&str>) -> Level { logger_level.into() } -fn redis_value_to_js(val: Value, js_env: Env) -> Result { +fn redis_value_to_js(val: Value, js_env: Env, string_decoder: bool) -> Result { match val { Value::Nil => js_env.get_null().map(|val| val.into_unknown()), - Value::SimpleString(str) => Ok(js_env - .create_buffer_with_data(str.as_bytes().to_vec())? - .into_unknown()), + Value::SimpleString(str) => { + if string_decoder { + Ok(js_env + .create_string_from_std(str) + .map(|val| val.into_unknown())?) + } else { + Ok(js_env + .create_buffer_with_data(str.as_bytes().to_vec())? + .into_unknown()) + } + } Value::Okay => js_env.create_string("OK").map(|val| val.into_unknown()), Value::Int(num) => js_env.create_int64(num).map(|val| val.into_unknown()), - Value::BulkString(data) => Ok(js_env.create_buffer_with_data(data)?.into_unknown()), + Value::BulkString(data) => { + if string_decoder { + let str = to_js_result(std::str::from_utf8(data.as_ref()))?; + Ok(js_env.create_string(str).map(|val| val.into_unknown())?) + } else { + Ok(js_env.create_buffer_with_data(data)?.into_unknown()) + } + } Value::Array(array) => { let mut js_array_view = js_env.create_array_with_length(array.len())?; for (index, item) in array.into_iter().enumerate() { - js_array_view.set_element(index as u32, redis_value_to_js(item, js_env)?)?; + js_array_view.set_element( + index as u32, + redis_value_to_js(item, js_env, string_decoder)?, + )?; } Ok(js_array_view.into_unknown()) } @@ -180,7 +198,7 @@ fn redis_value_to_js(val: Value, js_env: Env) -> Result { let mut obj = js_env.create_object()?; for (key, value) in map { let field_name = String::from_owned_redis_value(key).map_err(to_js_error)?; - let value = redis_value_to_js(value, js_env)?; + let value = redis_value_to_js(value, js_env, string_decoder)?; obj.set_named_property(&field_name, value)?; } Ok(obj.into_unknown()) @@ -192,10 +210,16 @@ fn redis_value_to_js(val: Value, js_env: Env) -> Result { // "type and the String type, and return a string in both cases."" // https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md Value::VerbatimString { format: _, text } => { - // VerbatimString is binary safe -> convert it into such - Ok(js_env - .create_buffer_with_data(text.as_bytes().to_vec())? - .into_unknown()) + if string_decoder { + Ok(js_env + .create_string_from_std(text) + .map(|val| val.into_unknown())?) + } else { + // VerbatimString is binary safe -> convert it into such + Ok(js_env + .create_buffer_with_data(text.as_bytes().to_vec())? + .into_unknown()) + } } Value::BigNumber(num) => { let sign = num.is_negative(); @@ -208,16 +232,19 @@ fn redis_value_to_js(val: Value, js_env: Env) -> Result { // TODO - return a set object instead of an array object let mut js_array_view = js_env.create_array_with_length(array.len())?; for (index, item) in array.into_iter().enumerate() { - js_array_view.set_element(index as u32, redis_value_to_js(item, js_env)?)?; + js_array_view.set_element( + index as u32, + redis_value_to_js(item, js_env, string_decoder)?, + )?; } Ok(js_array_view.into_unknown()) } Value::Attribute { data, attributes } => { let mut obj = js_env.create_object()?; - let value = redis_value_to_js(*data, js_env)?; + let value = redis_value_to_js(*data, js_env, string_decoder)?; obj.set_named_property("value", value)?; - let value = redis_value_to_js(Value::Map(attributes), js_env)?; + let value = redis_value_to_js(Value::Map(attributes), js_env, string_decoder)?; obj.set_named_property("attributes", value)?; Ok(obj.into_unknown()) @@ -227,7 +254,7 @@ fn redis_value_to_js(val: Value, js_env: Env) -> Result { obj.set_named_property("kind", format!("{kind:?}"))?; let js_array_view = data .into_iter() - .map(|item| redis_value_to_js(item, js_env)) + .map(|item| redis_value_to_js(item, js_env, string_decoder)) .collect::, _>>()?; obj.set_named_property("values", js_array_view)?; Ok(obj.into_unknown()) @@ -236,9 +263,14 @@ fn redis_value_to_js(val: Value, js_env: Env) -> Result { } #[napi( - ts_return_type = "null | string | Uint8Array | number | {} | Boolean | BigInt | Set | any[]" + ts_return_type = "null | string | Uint8Array | number | {} | Boolean | BigInt | Set | any[] | Buffer" )] -pub fn value_from_split_pointer(js_env: Env, high_bits: u32, low_bits: u32) -> Result { +pub fn value_from_split_pointer( + js_env: Env, + high_bits: u32, + low_bits: u32, + string_decoder: bool, +) -> Result { let mut bytes = [0_u8; 8]; (&mut bytes[..4]) .write_u32::(low_bits) @@ -248,7 +280,7 @@ pub fn value_from_split_pointer(js_env: Env, high_bits: u32, low_bits: u32) -> R .unwrap(); let pointer = u64::from_le_bytes(bytes); let value = unsafe { Box::from_raw(pointer as *mut Value) }; - redis_value_to_js(*value, js_env) + redis_value_to_js(*value, js_env, string_decoder) } // Pointers are split because JS cannot represent a full usize using its `number` object. diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 30884f72f8..2f1be67495 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -1,7 +1,6 @@ /** * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ - import { DEFAULT_TIMEOUT_IN_MILLISECONDS, Script, @@ -9,114 +8,231 @@ import { valueFromSplitPointer, } from "glide-rs"; import * as net from "net"; -import { Buffer, BufferWriter, Reader, Writer } from "protobufjs"; +import { Buffer, BufferWriter, Long, Reader, Writer } from "protobufjs"; import { AggregationType, + BaseScanOptions, + BitFieldGet, + BitFieldIncrBy, // eslint-disable-line @typescript-eslint/no-unused-vars + BitFieldOverflow, // eslint-disable-line @typescript-eslint/no-unused-vars + BitFieldSet, // eslint-disable-line @typescript-eslint/no-unused-vars + BitFieldSubCommands, + BitOffset, // eslint-disable-line @typescript-eslint/no-unused-vars + BitOffsetMultiplier, // eslint-disable-line @typescript-eslint/no-unused-vars + BitOffsetOptions, + BitmapIndexType, + BitwiseOperation, + Boundary, + CoordOrigin, // eslint-disable-line @typescript-eslint/no-unused-vars ExpireOptions, + GeoAddOptions, + GeoBoxShape, // eslint-disable-line @typescript-eslint/no-unused-vars + GeoCircleShape, // eslint-disable-line @typescript-eslint/no-unused-vars + GeoSearchResultOptions, + GeoSearchShape, + GeoSearchStoreResultOptions, + GeoUnit, + GeospatialData, InsertPosition, KeyWeight, + LPosOptions, + ListDirection, + MemberOrigin, // eslint-disable-line @typescript-eslint/no-unused-vars RangeByIndex, RangeByLex, RangeByScore, - ScoreBoundary, + RestoreOptions, + ReturnTypeXinfoStream, + ScoreFilter, + SearchOrigin, SetOptions, StreamAddOptions, + StreamClaimOptions, + StreamGroupOptions, + StreamPendingOptions, + StreamReadGroupOptions, StreamReadOptions, StreamTrimOptions, + TimeUnit, ZAddOptions, + createAppend, + createBLMPop, + createBLMove, createBLPop, createBRPop, + createBZMPop, + createBZPopMax, + createBZPopMin, + createBitCount, + createBitField, + createBitOp, + createBitPos, createDecr, createDecrBy, createDel, + createDump, createExists, createExpire, createExpireAt, + createExpireTime, + createFCall, + createFCallReadOnly, + createGeoAdd, + createGeoDist, + createGeoHash, + createGeoPos, + createGeoSearch, + createGeoSearchStore, createGet, + createGetBit, + createGetDel, + createGetEx, + createGetRange, createHDel, createHExists, createHGet, createHGetAll, createHIncrBy, createHIncrByFloat, + createHKeys, createHLen, createHMGet, + createHRandField, + createHScan, createHSet, createHSetNX, + createHStrlen, createHVals, createIncr, createIncrBy, createIncrByFloat, + createLCS, createLIndex, createLInsert, createLLen, + createLMPop, + createLMove, createLPop, + createLPos, createLPush, + createLPushX, createLRange, createLRem, createLSet, createLTrim, createMGet, createMSet, + createMSetNX, createObjectEncoding, createObjectFreq, createObjectIdletime, createObjectRefcount, createPExpire, createPExpireAt, + createPExpireTime, createPTTL, createPersist, createPfAdd, createPfCount, + createPfMerge, + createPubSubChannels, + createPubSubNumPat, + createPubSubNumSub, createRPop, createRPush, + createRPushX, createRename, createRenameNX, + createRestore, createSAdd, createSCard, createSDiff, createSDiffStore, createSInter, + createSInterCard, createSInterStore, createSIsMember, + createSMIsMember, createSMembers, createSMove, createSPop, + createSRandMember, createSRem, + createSScan, + createSUnion, createSUnionStore, createSet, + createSetBit, + createSetRange, createStrlen, createTTL, + createTouch, createType, createUnlink, + createWait, + createWatch, + createXAck, createXAdd, + createXAutoClaim, + createXClaim, + createXDel, + createXGroupCreate, + createXGroupCreateConsumer, + createXGroupDelConsumer, + createXGroupDestroy, + createXGroupSetid, + createXInfoConsumers, + createXInfoGroups, + createXInfoStream, createXLen, + createXPending, + createXRange, createXRead, + createXReadGroup, + createXRevRange, createXTrim, createZAdd, createZCard, createZCount, + createZDiff, + createZDiffStore, + createZDiffWithScores, + createZIncrBy, + createZInter, createZInterCard, createZInterstore, + createZLexCount, + createZMPop, + createZMScore, createZPopMax, createZPopMin, + createZRandMember, createZRange, + createZRangeStore, createZRangeWithScores, createZRank, createZRem, + createZRemRangeByLex, createZRemRangeByRank, createZRemRangeByScore, + createZRevRank, + createZRevRankWithScore, + createZScan, createZScore, - createSUnion, + createZUnion, + createZUnionStore, } from "./Commands"; import { ClosingError, + ConfigurationError, ConnectionError, ExecAbortError, RedisError, RequestError, TimeoutError, } from "./Errors"; +import { GlideClientConfiguration } from "./GlideClient"; +import { GlideClusterClientConfiguration } from "./GlideClusterClient"; import { Logger } from "./Logger"; import { command_request, @@ -127,10 +243,11 @@ import { /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ type PromiseFunction = (value?: any) => void; type ErrorFunction = (error: RedisError) => void; -export type ReturnTypeMap = { [key: string]: ReturnType }; +export type ReturnTypeRecord = { [key: string]: ReturnType }; +export type ReturnTypeMap = Map; export type ReturnTypeAttribute = { value: ReturnType; - attributes: ReturnTypeMap; + attributes: ReturnTypeRecord; }; export enum ProtocolVersion { /** Use RESP2 to communicate with the server nodes. */ @@ -145,30 +262,120 @@ export type ReturnType = | null | boolean | bigint + | Buffer | Set + | ReturnTypeRecord | ReturnTypeMap | ReturnTypeAttribute | ReturnType[]; -type RedisCredentials = { +/** + * Union type that can store either a valid UTF-8 string or array of bytes. + */ +export type GlideString = string | Buffer; + +/** + * Enum representing the different types of decoders. + */ +export enum Decoder { + /** + * Decodes the response into a buffer array. + */ + Bytes, + /** + * Decodes the response into a string. + */ + String, +} + +/** An extension to command option types with {@link Decoder}. */ +export type DecoderOption = { + /** + * {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. + */ + decoder?: Decoder; +}; + +/** + * This function converts an input from HashDataType or Record types to HashDataType. + * + * @param fieldsAndValues - field names and their values. + * @returns HashDataType array containing field names and their values. + */ +export function convertFieldsAndValuesForHset( + fieldsAndValues: HashDataType | Record, +): HashDataType { + let finalFieldAndValues = []; + + if (!Array.isArray(fieldsAndValues)) { + finalFieldAndValues = Object.entries(fieldsAndValues).map((e) => { + return { field: e[0], value: e[1] }; + }); + } else { + finalFieldAndValues = fieldsAndValues; + } + + return finalFieldAndValues; +} + +/** + * Our purpose in creating PointerResponse type is to mark when response is of number/long pointer response type. + * Consequently, when the response is returned, we can check whether it is instanceof the PointerResponse type and pass it to the Rust core function with the proper parameters. + */ +class PointerResponse { + pointer: number | Long | null; + // As Javascript does not support 64-bit integers, + // we split the Rust u64 pointer into two u32 integers (high and low) and build it again when we call value_from_split_pointer, the Rust function. + high: number | undefined; + low: number | undefined; + + constructor( + pointer: number | Long | null, + high?: number | undefined, + low?: number | undefined, + ) { + this.pointer = pointer; + this.high = high; + this.low = low; + } +} + +/** + * Data type which represents how data are returned from hashes or insterted there. + * Similar to `Record` - see {@link GlideRecord}. + */ +export type HashDataType = { + /** The hash element name. */ + field: GlideString; + /** The hash element value. */ + value: GlideString; +}[]; + +/** Represents the credentials for connecting to a server. */ +export type RedisCredentials = { /** - * The username that will be used for authenticating connections to the Redis servers. + * The username that will be used for authenticating connections to the Valkey servers. * If not supplied, "default" will be used. */ username?: string; /** - * The password that will be used for authenticating connections to the Redis servers. + * The password that will be used for authenticating connections to the Valkey servers. */ password: string; }; -type ReadFrom = +/** Represents the client's read from strategy. */ +export type ReadFrom = /** Always get from primary, in order to get the freshest data.*/ | "primary" /** Spread the requests between all replicas in a round robin manner. If no replica is available, route the requests to the primary.*/ | "preferReplica"; +/** + * Configuration settings for creating a client. Shared settings for standalone and cluster clients. + */ export type BaseClientConfiguration = { /** * DNS Addresses and ports of known nodes in the cluster. @@ -225,17 +432,26 @@ export type BaseClientConfiguration = { * Client name to be used for the client. Will be used with CLIENT SETNAME command during connection establishment. */ clientName?: string; + /** + * Default decoder when decoder is not set per command. + * If not set, 'Decoder.String' will be used. + */ + defaultDecoder?: Decoder; }; export type ScriptOptions = { /** * The keys that are used in the script. */ - keys?: (string | Uint8Array)[]; + keys?: GlideString[]; /** * The arguments for the script. */ - args?: (string | Uint8Array)[]; + args?: GlideString[]; + /** + * {@link Decoder} type which defines how to handle the responses. If not set, the default decoder from the client config will be used. + */ + decoder?: Decoder; }; function getRequestErrorClass( @@ -260,6 +476,16 @@ function getRequestErrorClass( return RequestError; } +export type PubSubMsg = { + message: string; + channel: string; + pattern?: string | null; +}; + +export type WritePromiseOptions = { + decoder?: Decoder; + route?: command_request.Routes; +}; export class BaseClient { private socket: net.Socket; private readonly promiseCallbackFunctions: [ @@ -272,7 +498,55 @@ export class BaseClient { private remainingReadData: Uint8Array | undefined; private readonly requestTimeout: number; // Timeout in milliseconds private isClosed = false; + protected defaultDecoder = Decoder.String; + private readonly pubsubFutures: [PromiseFunction, ErrorFunction][] = []; + private pendingPushNotification: response.Response[] = []; + private config: BaseClientConfiguration | undefined; + + protected configurePubsub( + options: GlideClusterClientConfiguration | GlideClientConfiguration, + configuration: connection_request.IConnectionRequest, + ) { + if (options.pubsubSubscriptions) { + if (options.protocol == ProtocolVersion.RESP2) { + throw new ConfigurationError( + "PubSub subscriptions require RESP3 protocol, but RESP2 was configured.", + ); + } + + const { context, callback } = options.pubsubSubscriptions; + + if (context && !callback) { + throw new ConfigurationError( + "PubSub subscriptions with a context require a callback function to be configured.", + ); + } + + configuration.pubsubSubscriptions = + connection_request.PubSubSubscriptions.create({}); + + for (const [channelType, channelsPatterns] of Object.entries( + options.pubsubSubscriptions.channelsAndPatterns, + )) { + let entry = + configuration.pubsubSubscriptions! + .channelsOrPatternsByType![parseInt(channelType)]; + + if (!entry) { + entry = connection_request.PubSubChannelsOrPatterns.create({ + channelsOrPatterns: [], + }); + configuration.pubsubSubscriptions!.channelsOrPatternsByType![ + parseInt(channelType) + ] = entry; + } + for (const channelPattern of channelsPatterns) { + entry.channelsOrPatterns!.push(Buffer.from(channelPattern)); + } + } + } + } private handleReadData(data: Buffer) { const buf = this.remainingReadData ? Buffer.concat([this.remainingReadData, data]) @@ -300,40 +574,77 @@ export class BaseClient { } } - if (message.closingError != null) { - this.close(message.closingError); - return; + if (message.isPush) { + this.processPush(message); + } else { + this.processResponse(message); } + } - const [resolve, reject] = - this.promiseCallbackFunctions[message.callbackIdx]; - this.availableCallbackSlots.push(message.callbackIdx); + this.remainingReadData = undefined; + } - if (message.requestError != null) { - const errorType = getRequestErrorClass( - message.requestError.type, - ); - reject( - new errorType(message.requestError.message ?? undefined), - ); - } else if (message.respPointer != null) { - const pointer = message.respPointer; + processResponse(message: response.Response) { + if (message.closingError != null) { + this.close(message.closingError); + return; + } - if (typeof pointer === "number") { - resolve(valueFromSplitPointer(0, pointer)); - } else { - resolve(valueFromSplitPointer(pointer.high, pointer.low)); - } - } else if ( - message.constantResponse === response.ConstantResponse.OK - ) { - resolve("OK"); + const [resolve, reject] = + this.promiseCallbackFunctions[message.callbackIdx]; + this.availableCallbackSlots.push(message.callbackIdx); + + if (message.requestError != null) { + const errorType = getRequestErrorClass(message.requestError.type); + reject(new errorType(message.requestError.message ?? undefined)); + } else if (message.respPointer != null) { + let pointer; + + if (typeof message.respPointer === "number") { + // Response from type number + pointer = new PointerResponse(message.respPointer); } else { - resolve(null); + // Response from type long + pointer = new PointerResponse( + message.respPointer, + message.respPointer.high, + message.respPointer.low, + ); } + + resolve(pointer); + } else if (message.constantResponse === response.ConstantResponse.OK) { + resolve("OK"); + } else { + resolve(null); + } + } + + processPush(response: response.Response) { + if (response.closingError != null || !response.respPointer) { + const errMsg = response.closingError + ? response.closingError + : "Client Error - push notification without resp_pointer"; + + this.close(errMsg); + return; } - this.remainingReadData = undefined; + const [callback, context] = this.getPubsubCallbackAndContext( + this.config!, + ); + + if (callback) { + const pubsubMessage = + this.notificationToPubSubMessageSafe(response); + + if (pubsubMessage) { + callback(pubsubMessage, context); + } + } else { + this.pendingPushNotification.push(response); + this.completePubSubFuturesSafe(); + } } /** @@ -345,6 +656,7 @@ export class BaseClient { ) { // if logger has been initialized by the external-user on info level this log will be shown Logger.log("info", "Client lifetime", `construct client`); + this.config = options; this.requestTimeout = options?.requestTimeout ?? DEFAULT_TIMEOUT_IN_MILLISECONDS; this.socket = socket; @@ -354,6 +666,7 @@ export class BaseClient { console.error(`Server closed: ${err}`); this.close(); }); + this.defaultDecoder = options?.defaultDecoder ?? Decoder.String; } private getCallbackIndex(): number { @@ -385,8 +698,11 @@ export class BaseClient { | command_request.Command | command_request.Command[] | command_request.ScriptInvocation, - route?: command_request.Routes, + options: WritePromiseOptions = {}, ): Promise { + const { decoder = this.defaultDecoder, route } = options; + const stringDecoder = decoder === Decoder.String ? true : false; + if (this.isClosed) { throw new ClosingError( "Unable to execute requests; the client is closed. Please create a new client.", @@ -395,7 +711,37 @@ export class BaseClient { return new Promise((resolve, reject) => { const callbackIndex = this.getCallbackIndex(); - this.promiseCallbackFunctions[callbackIndex] = [resolve, reject]; + this.promiseCallbackFunctions[callbackIndex] = [ + (resolveAns: T) => { + try { + if (resolveAns instanceof PointerResponse) { + if (typeof resolveAns === "number") { + resolveAns = valueFromSplitPointer( + 0, + resolveAns, + stringDecoder, + ) as T; + } else { + resolveAns = valueFromSplitPointer( + resolveAns.high!, + resolveAns.low!, + stringDecoder, + ) as T; + } + } + + resolve(resolveAns); + } catch (err) { + Logger.log( + "error", + "Decoder", + `Decoding error: '${err}'`, + ); + reject(err); + } + }, + reject, + ]; this.writeOrBufferCommandRequest(callbackIndex, command, route); }); } @@ -466,29 +812,310 @@ export class BaseClient { return result; } + cancelPubSubFuturesWithExceptionSafe(exception: ConnectionError): void { + while (this.pubsubFutures.length > 0) { + const nextFuture = this.pubsubFutures.shift(); + + if (nextFuture) { + const [, reject] = nextFuture; + reject(exception); + } + } + } + + isPubsubConfigured( + config: GlideClientConfiguration | GlideClusterClientConfiguration, + ): boolean { + return !!config.pubsubSubscriptions; + } + + getPubsubCallbackAndContext( + config: GlideClientConfiguration | GlideClusterClientConfiguration, + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + ): [((msg: PubSubMsg, context: any) => void) | null | undefined, any] { + if (config.pubsubSubscriptions) { + return [ + config.pubsubSubscriptions.callback, + config.pubsubSubscriptions.context, + ]; + } + + return [null, null]; + } + + public async getPubSubMessage(): Promise { + if (this.isClosed) { + throw new ClosingError( + "Unable to execute requests; the client is closed. Please create a new client.", + ); + } + + if (!this.isPubsubConfigured(this.config!)) { + throw new ConfigurationError( + "The operation will never complete since there was no pubsbub subscriptions applied to the client.", + ); + } + + if (this.getPubsubCallbackAndContext(this.config!)[0]) { + throw new ConfigurationError( + "The operation will never complete since messages will be passed to the configured callback.", + ); + } + + return new Promise((resolve, reject) => { + this.pubsubFutures.push([resolve, reject]); + this.completePubSubFuturesSafe(); + }); + } + + public tryGetPubSubMessage(decoder?: Decoder): PubSubMsg | null { + if (this.isClosed) { + throw new ClosingError( + "Unable to execute requests; the client is closed. Please create a new client.", + ); + } + + if (!this.isPubsubConfigured(this.config!)) { + throw new ConfigurationError( + "The operation will never complete since there was no pubsbub subscriptions applied to the client.", + ); + } + + if (this.getPubsubCallbackAndContext(this.config!)[0]) { + throw new ConfigurationError( + "The operation will never complete since messages will be passed to the configured callback.", + ); + } + + let msg: PubSubMsg | null = null; + this.completePubSubFuturesSafe(); + + while (this.pendingPushNotification.length > 0 && !msg) { + const pushNotification = this.pendingPushNotification.shift()!; + msg = this.notificationToPubSubMessageSafe( + pushNotification, + decoder, + ); + } + + return msg; + } + notificationToPubSubMessageSafe( + pushNotification: response.Response, + decoder?: Decoder, + ): PubSubMsg | null { + let msg: PubSubMsg | null = null; + const responsePointer = pushNotification.respPointer; + let nextPushNotificationValue: Record = {}; + + if (responsePointer) { + if (typeof responsePointer !== "number") { + nextPushNotificationValue = valueFromSplitPointer( + responsePointer.high, + responsePointer.low, + decoder === Decoder.String, + ) as Record; + } else { + nextPushNotificationValue = valueFromSplitPointer( + 0, + responsePointer, + decoder === Decoder.String, + ) as Record; + } + + const messageKind = nextPushNotificationValue["kind"]; + + if (messageKind === "Disconnect") { + Logger.log( + "warn", + "disconnect notification", + "Transport disconnected, messages might be lost", + ); + } else if ( + messageKind === "Message" || + messageKind === "PMessage" || + messageKind === "SMessage" + ) { + const values = nextPushNotificationValue["values"] as string[]; + + if (messageKind === "PMessage") { + msg = { + message: values[2], + channel: values[1], + pattern: values[0], + }; + } else { + msg = { + message: values[1], + channel: values[0], + pattern: null, + }; + } + } else if ( + messageKind === "PSubscribe" || + messageKind === "Subscribe" || + messageKind === "SSubscribe" || + messageKind === "Unsubscribe" || + messageKind === "SUnsubscribe" || + messageKind === "PUnsubscribe" + ) { + // pass + } else { + Logger.log( + "error", + "unknown notification", + `Unknown notification: '${messageKind}'`, + ); + } + } + + return msg; + } + completePubSubFuturesSafe() { + while ( + this.pendingPushNotification.length > 0 && + this.pubsubFutures.length > 0 + ) { + const nextPushNotification = this.pendingPushNotification.shift()!; + const pubsubMessage = + this.notificationToPubSubMessageSafe(nextPushNotification); + + if (pubsubMessage) { + const [resolve] = this.pubsubFutures.shift()!; + resolve(pubsubMessage); + } + } + } + /** Get the value associated with the given key, or null if no such value exists. - * See https://valkey.io/commands/get/ for details. + * + * @see {@link https://valkey.io/commands/get/|valkey.io} for details. * * @param key - The key to retrieve from the database. - * @returns If `key` exists, returns the value of `key` as a string. Otherwise, return null. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. + * @returns If `key` exists, returns the value of `key`. Otherwise, return null. * * @example * ```typescript * // Example usage of get method to retrieve the value of a key * const result = await client.get("key"); * console.log(result); // Output: 'value' + * // Example usage of get method to retrieve the value of a key with Bytes decoder + * const result = await client.get("key", Decoder.Bytes); + * console.log(result); // Output: {"data": [118, 97, 108, 117, 101], "type": "Buffer"} + * ``` + */ + public async get( + key: GlideString, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createGet(key), { decoder: decoder }); + } + + /** + * Get the value of `key` and optionally set its expiration. `GETEX` is similar to {@link get}. + * + * @see {@link https://valkey.io/commands/getex/|valkey.op} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key to retrieve from the database. + * @param options - (Optional) Additional Parameters: + * - (Optional) `expiry`: expiriation to the given key: + * `"persist"` will retain the time to live associated with the key. Equivalent to `PERSIST` in the VALKEY API. + * Otherwise, a {@link TimeUnit} and duration of the expire time should be specified. + * - (Optional) `decoder`: see {@link DecoderOption}. + * @returns If `key` exists, returns the value of `key` as a `string`. Otherwise, return `null`. + * + * @example + * ```typescript + * const result = await client.getex("key", {expiry: { type: TimeUnit.Seconds, count: 5 }}); + * console.log(result); // Output: 'value' + * ``` + */ + public async getex( + key: GlideString, + options?: { + expiry: "persist" | { type: TimeUnit; duration: number }; + } & DecoderOption, + ): Promise { + return this.createWritePromise(createGetEx(key, options?.expiry), { + decoder: options?.decoder, + }); + } + + /** + * Gets a string value associated with the given `key`and deletes the key. + * + * @see {@link https://valkey.io/commands/getdel/|valkey.io} for details. + * + * @param key - The key to retrieve from the database. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. + * @returns If `key` exists, returns the `value` of `key`. Otherwise, return `null`. + * + * @example + * ```typescript + * const result = client.getdel("key"); + * console.log(result); // Output: 'value' + * + * const value = client.getdel("key"); // value is null + * ``` + */ + public async getdel( + key: GlideString, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createGetDel(key), { decoder: decoder }); + } + + /** + * Returns the substring of the string value stored at `key`, determined by the offsets + * `start` and `end` (both are inclusive). Negative offsets can be used in order to provide + * an offset starting from the end of the string. So `-1` means the last character, `-2` the + * penultimate and so forth. If `key` does not exist, an empty string is returned. If `start` + * or `end` are out of range, returns the substring within the valid range of the string. + * + * @see {@link https://valkey.io/commands/getrange/|valkey.io} for details. + * + * @param key - The key of the string. + * @param start - The starting offset. + * @param end - The ending offset. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. + * @returns A substring extracted from the value stored at `key`. + * + * @example + * ```typescript + * await client.set("mykey", "This is a string") + * let result = await client.getrange("mykey", 0, 3) + * console.log(result); // Output: "This" + * result = await client.getrange("mykey", -3, -1) + * console.log(result); // Output: "ing" - extracted last 3 characters of a string + * result = await client.getrange("mykey", 0, 100) + * console.log(result); // Output: "This is a string" + * result = await client.getrange("mykey", 5, 6) + * console.log(result); // Output: "" * ``` */ - public get(key: string): Promise { - return this.createWritePromise(createGet(key)); + public async getrange( + key: GlideString, + start: number, + end: number, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createGetRange(key, start, end), { + decoder: decoder, + }); } /** Set the given key with the given value. Return value is dependent on the passed options. - * See https://valkey.io/commands/set/ for details. + * + * @see {@link https://valkey.io/commands/set/|valkey.io} for details. * * @param key - The key to store. * @param value - The value to store with the given key. - * @param options - The set options. + * @param options - (Optional) See {@link SetOptions} and {@link DecoderOption}. * @returns - If the value is successfully set, return OK. * If value isn't set because of `onlyIfExists` or `onlyIfDoesNotExist` conditions, return null. * If `returnOldValue` is set, return the old value as a string. @@ -500,7 +1127,7 @@ export class BaseClient { * console.log(result); // Output: 'OK' * * // Example usage of set method with conditional options and expiration - * const result2 = await client.set("key", "new_value", {conditionalSet: "onlyIfExists", expiry: { type: "seconds", count: 5 }}); + * const result2 = await client.set("key", "new_value", {conditionalSet: "onlyIfExists", expiry: { type: TimeUnit.Seconds, count: 5 }}); * console.log(result2); // Output: 'OK' - Set "new_value" to "key" only if "key" already exists, and set the key expiration to 5 seconds. * * // Example usage of set method with conditional options and returning old value @@ -512,19 +1139,23 @@ export class BaseClient { * console.log(result4); // Output: 'new_value' - Value wasn't modified back to being "value" because of "NX" flag. * ``` */ - public set( - key: string | Uint8Array, - value: string | Uint8Array, - options?: SetOptions, - ): Promise<"OK" | string | null> { - return this.createWritePromise(createSet(key, value, options)); + public async set( + key: GlideString, + value: GlideString, + options?: SetOptions & DecoderOption, + ): Promise<"OK" | GlideString | null> { + return this.createWritePromise(createSet(key, value, options), { + decoder: options?.decoder, + }); } - /** Removes the specified keys. A key is ignored if it does not exist. - * See https://valkey.io/commands/del/ for details. + /** + * Removes the specified keys. A key is ignored if it does not exist. + * + * @see {@link https://valkey.io/commands/del/|valkey.io} for details. * - * @param keys - the keys we wanted to remove. - * @returns the number of keys that were removed. + * @param keys - The keys we wanted to remove. + * @returns The number of keys that were removed. * * @example * ```typescript @@ -541,15 +1172,93 @@ export class BaseClient { * console.log(result); // Output: 0 * ``` */ - public del(keys: string[]): Promise { + public async del(keys: GlideString[]): Promise { return this.createWritePromise(createDel(keys)); } + /** + * Serialize the value stored at `key` in a Valkey-specific format and return it to the user. + * + * @see {@link https://valkey.io/commands/dump/|valkey.io} for details. + * + * @param key - The `key` to serialize. + * @returns The serialized value of the data stored at `key`. If `key` does not exist, `null` will be returned. + * + * @example + * ```typescript + * let result = await client.dump("myKey"); + * console.log(result); // Output: the serialized value of "myKey" + * ``` + * + * @example + * ```typescript + * result = await client.dump("nonExistingKey"); + * console.log(result); // Output: `null` + * ``` + */ + public async dump(key: GlideString): Promise { + return this.createWritePromise(createDump(key), { + decoder: Decoder.Bytes, + }); + } + + /** + * Create a `key` associated with a `value` that is obtained by deserializing the provided + * serialized `value` (obtained via {@link dump}). + * + * @see {@link https://valkey.io/commands/restore/|valkey.io} for details. + * @remarks `options.idletime` and `options.frequency` modifiers cannot be set at the same time. + * + * @param key - The `key` to create. + * @param ttl - The expiry time (in milliseconds). If `0`, the `key` will persist. + * @param value - The serialized value to deserialize and assign to `key`. + * @param options - (Optional) Restore options {@link RestoreOptions}. + * @returns Return "OK" if the `key` was successfully restored with a `value`. + * + * @example + * ```typescript + * const result = await client.restore("myKey", 0, value); + * console.log(result); // Output: "OK" + * ``` + * + * @example + * ```typescript + * const result = await client.restore("myKey", 1000, value, {replace: true, absttl: true}); + * console.log(result); // Output: "OK" + * ``` + * + * @example + * ```typescript + * const result = await client.restore("myKey", 0, value, {replace: true, idletime: 10}); + * console.log(result); // Output: "OK" + * ``` + * + * @example + * ```typescript + * const result = await client.restore("myKey", 0, value, {replace: true, frequency: 10}); + * console.log(result); // Output: "OK" + * ``` + */ + public async restore( + key: GlideString, + ttl: number, + value: Buffer, + options?: RestoreOptions, + ): Promise<"OK"> { + return this.createWritePromise( + createRestore(key, ttl, value, options), + { decoder: Decoder.String }, + ); + } + /** Retrieve the values of multiple keys. - * See https://valkey.io/commands/mget/ for details. * + * @see {@link https://valkey.io/commands/mget/|valkey.io} for details. * @remarks When in cluster mode, the command may route to multiple nodes when `keys` map to different hash slots. + * * @param keys - A list of keys to retrieve values for. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * @returns A list of values corresponding to the provided keys. If a key is not found, * its corresponding value in the list will be null. * @@ -562,14 +1271,18 @@ export class BaseClient { * console.log(result); // Output: ['value1', 'value2'] * ``` */ - public mget(keys: string[]): Promise<(string | null)[]> { - return this.createWritePromise(createMGet(keys)); + public async mget( + keys: GlideString[], + decoder?: Decoder, + ): Promise<(GlideString | null)[]> { + return this.createWritePromise(createMGet(keys), { decoder: decoder }); } /** Set multiple keys to multiple values in a single operation. - * See https://valkey.io/commands/mset/ for details. * + * @see {@link https://valkey.io/commands/mset/|valkey.io} for details. * @remarks When in cluster mode, the command may route to multiple nodes when keys in `keyValueMap` map to different hash slots. + * * @param keyValueMap - A key-value map consisting of keys and their respective values to set. * @returns always "OK". * @@ -580,12 +1293,36 @@ export class BaseClient { * console.log(result); // Output: 'OK' * ``` */ - public mset(keyValueMap: Record): Promise<"OK"> { + public async mset(keyValueMap: Record): Promise<"OK"> { return this.createWritePromise(createMSet(keyValueMap)); } + /** + * Sets multiple keys to values if the key does not exist. The operation is atomic, and if one or + * more keys already exist, the entire operation fails. + * + * @see {@link https://valkey.io/commands/msetnx/|valkey.io} for more details. + * @remarks When in cluster mode, all keys in `keyValueMap` must map to the same hash slot. + * + * @param keyValueMap - A key-value map consisting of keys and their respective values to set. + * @returns `true` if all keys were set. `false` if no key was set. + * + * @example + * ```typescript + * const result1 = await client.msetnx({"key1": "value1", "key2": "value2"}); + * console.log(result1); // Output: `true` + * + * const result2 = await client.msetnx({"key2": "value4", "key3": "value5"}); + * console.log(result2); // Output: `false` + * ``` + */ + public async msetnx(keyValueMap: Record): Promise { + return this.createWritePromise(createMSetNX(keyValueMap)); + } + /** Increments the number stored at `key` by one. If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/incr/ for details. + * + * @see {@link https://valkey.io/commands/incr/|valkey.io} for details. * * @param key - The key to increment its value. * @returns the value of `key` after the increment. @@ -598,12 +1335,13 @@ export class BaseClient { * console.log(result); // Output: 11 * ``` */ - public incr(key: string): Promise { + public async incr(key: GlideString): Promise { return this.createWritePromise(createIncr(key)); } /** Increments the number stored at `key` by `amount`. If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/incrby/ for details. + * + * @see {@link https://valkey.io/commands/incrby/|valkey.io} for details. * * @param key - The key to increment its value. * @param amount - The amount to increment. @@ -617,14 +1355,15 @@ export class BaseClient { * console.log(result); // Output: 15 * ``` */ - public incrBy(key: string, amount: number): Promise { + public async incrBy(key: GlideString, amount: number): Promise { return this.createWritePromise(createIncrBy(key, amount)); } /** Increment the string representing a floating point number stored at `key` by `amount`. * By using a negative increment value, the result is that the value stored at `key` is decremented. * If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/incrbyfloat/ for details. + * + * @see {@link https://valkey.io/commands/incrbyfloat/|valkey.io} for details. * * @param key - The key to increment its value. * @param amount - The amount to increment. @@ -638,12 +1377,16 @@ export class BaseClient { * console.log(result); // Output: 13.0 * ``` */ - public incrByFloat(key: string, amount: number): Promise { + public async incrByFloat( + key: GlideString, + amount: number, + ): Promise { return this.createWritePromise(createIncrByFloat(key, amount)); } /** Decrements the number stored at `key` by one. If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/decr/ for details. + * + * @see {@link https://valkey.io/commands/decr/|valkey.io} for details. * * @param key - The key to decrement its value. * @returns the value of `key` after the decrement. @@ -656,12 +1399,13 @@ export class BaseClient { * console.log(result); // Output: 9 * ``` */ - public decr(key: string): Promise { + public async decr(key: GlideString): Promise { return this.createWritePromise(createDecr(key)); } /** Decrements the number stored at `key` by `amount`. If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/decrby/ for details. + * + * @see {@link https://valkey.io/commands/decrby/|valkey.io} for details. * * @param key - The key to decrement its value. * @param amount - The amount to decrement. @@ -675,21 +1419,243 @@ export class BaseClient { * console.log(result); // Output: 5 * ``` */ - public decrBy(key: string, amount: number): Promise { + public async decrBy(key: GlideString, amount: number): Promise { return this.createWritePromise(createDecrBy(key, amount)); } - /** Retrieve the value associated with `field` in the hash stored at `key`. - * See https://valkey.io/commands/hget/ for details. + /** + * Perform a bitwise operation between multiple keys (containing string values) and store the result in the + * `destination`. + * + * @see {@link https://valkey.io/commands/bitop/|valkey.io} for more details. + * @remarks When in cluster mode, `destination` and all `keys` must map to the same hash slot. + * + * @param operation - The bitwise operation to perform. + * @param destination - The key that will store the resulting string. + * @param keys - The list of keys to perform the bitwise operation on. + * @returns The size of the string stored in `destination`. + * + * @example + * ```typescript + * await client.set("key1", "A"); // "A" has binary value 01000001 + * await client.set("key2", "B"); // "B" has binary value 01000010 + * const result1 = await client.bitop(BitwiseOperation.AND, "destination", ["key1", "key2"]); + * console.log(result1); // Output: 1 - The size of the resulting string stored in "destination" is 1. + * + * const result2 = await client.get("destination"); + * console.log(result2); // Output: "@" - "@" has binary value 01000000 + * ``` + */ + public async bitop( + operation: BitwiseOperation, + destination: GlideString, + keys: GlideString[], + ): Promise { + return this.createWritePromise( + createBitOp(operation, destination, keys), + ); + } + + /** + * Returns the bit value at `offset` in the string value stored at `key`. `offset` must be greater than or equal + * to zero. + * + * @see {@link https://valkey.io/commands/getbit/|valkey.io} for more details. + * + * @param key - The key of the string. + * @param offset - The index of the bit to return. + * @returns The bit at the given `offset` of the string. Returns `0` if the key is empty or if the `offset` exceeds + * the length of the string. + * + * @example + * ```typescript + * const result = await client.getbit("key", 1); + * console.log(result); // Output: 1 - The second bit of the string stored at "key" is set to 1. + * ``` + */ + public async getbit(key: GlideString, offset: number): Promise { + return this.createWritePromise(createGetBit(key, offset)); + } + + /** + * Sets or clears the bit at `offset` in the string value stored at `key`. The `offset` is a zero-based index, with + * `0` being the first element of the list, `1` being the next element, and so on. The `offset` must be less than + * `2^32` and greater than or equal to `0`. If a key is non-existent then the bit at `offset` is set to `value` and + * the preceding bits are set to `0`. + * + * @see {@link https://valkey.io/commands/setbit/|valkey.io} for more details. + * + * @param key - The key of the string. + * @param offset - The index of the bit to be set. + * @param value - The bit value to set at `offset`. The value must be `0` or `1`. + * @returns The bit value that was previously stored at `offset`. + * + * @example + * ```typescript + * const result = await client.setbit("key", 1, 1); + * console.log(result); // Output: 0 - The second bit value was 0 before setting to 1. + * ``` + */ + public async setbit( + key: GlideString, + offset: number, + value: number, + ): Promise { + return this.createWritePromise(createSetBit(key, offset, value)); + } + + /** + * Returns the position of the first bit matching the given `bit` value. The optional starting offset + * `start` is a zero-based index, with `0` being the first byte of the list, `1` being the next byte and so on. + * The offset can also be a negative number indicating an offset starting at the end of the list, with `-1` being + * the last byte of the list, `-2` being the penultimate, and so on. + * + * @see {@link https://valkey.io/commands/bitpos/|valkey.io} for more details. + * + * @param key - The key of the string. + * @param bit - The bit value to match. Must be `0` or `1`. + * @param start - (Optional) The starting offset. If not supplied, the search will start at the beginning of the string. + * @returns The position of the first occurrence of `bit` in the binary value of the string held at `key`. + * If `start` was provided, the search begins at the offset indicated by `start`. + * + * @example + * ```typescript + * await client.set("key1", "A1"); // "A1" has binary value 01000001 00110001 + * const result1 = await client.bitpos("key1", 1); + * console.log(result1); // Output: 1 - The first occurrence of bit value 1 in the string stored at "key1" is at the second position. + * + * const result2 = await client.bitpos("key1", 1, -1); + * console.log(result2); // Output: 10 - The first occurrence of bit value 1, starting at the last byte in the string stored at "key1", is at the eleventh position. + * ``` + */ + public async bitpos( + key: GlideString, + bit: number, + start?: number, + ): Promise { + return this.createWritePromise(createBitPos(key, bit, start)); + } + + /** + * Returns the position of the first bit matching the given `bit` value. The offsets are zero-based indexes, with + * `0` being the first element of the list, `1` being the next, and so on. These offsets can also be negative + * numbers indicating offsets starting at the end of the list, with `-1` being the last element of the list, `-2` + * being the penultimate, and so on. + * + * If you are using Valkey 7.0.0 or above, the optional `indexType` can also be provided to specify whether the + * `start` and `end` offsets specify BIT or BYTE offsets. If `indexType` is not provided, BYTE offsets + * are assumed. If BIT is specified, `start=0` and `end=2` means to look at the first three bits. If BYTE is + * specified, `start=0` and `end=2` means to look at the first three bytes. + * + * @see {@link https://valkey.io/commands/bitpos/|valkey.io} for more details. + * + * @param key - The key of the string. + * @param bit - The bit value to match. Must be `0` or `1`. + * @param start - The starting offset. + * @param end - The ending offset. + * @param indexType - (Optional) The index offset type. This option can only be specified if you are using Valkey + * version 7.0.0 or above. Could be either {@link BitmapIndexType.BYTE} or {@link BitmapIndexType.BIT}. If no + * index type is provided, the indexes will be assumed to be byte indexes. + * @returns The position of the first occurrence from the `start` to the `end` offsets of the `bit` in the binary + * value of the string held at `key`. + * + * @example + * ```typescript + * await client.set("key1", "A12"); // "A12" has binary value 01000001 00110001 00110010 + * const result1 = await client.bitposInterval("key1", 1, 1, -1); + * console.log(result1); // Output: 10 - The first occurrence of bit value 1 in the second byte to the last byte of the string stored at "key1" is at the eleventh position. + * + * const result2 = await client.bitposInterval("key1", 1, 2, 9, BitmapIndexType.BIT); + * console.log(result2); // Output: 7 - The first occurrence of bit value 1 in the third to tenth bits of the string stored at "key1" is at the eighth position. + * ``` + */ + public async bitposInterval( + key: GlideString, + bit: number, + start: number, + end: number, + indexType?: BitmapIndexType, + ): Promise { + return this.createWritePromise( + createBitPos(key, bit, start, end, indexType), + ); + } + + /** + * Reads or modifies the array of bits representing the string that is held at `key` based on the specified + * `subcommands`. + * + * @see {@link https://valkey.io/commands/bitfield/|valkey.io} for more details. + * + * @param key - The key of the string. + * @param subcommands - The subcommands to be performed on the binary value of the string at `key`, which could be + * any of the following: + * + * - {@link BitFieldGet} + * - {@link BitFieldSet} + * - {@link BitFieldIncrBy} + * - {@link BitFieldOverflow} + * + * @returns An array of results from the executed subcommands: + * + * - {@link BitFieldGet} returns the value in {@link BitOffset} or {@link BitOffsetMultiplier}. + * - {@link BitFieldSet} returns the old value in {@link BitOffset} or {@link BitOffsetMultiplier}. + * - {@link BitFieldIncrBy} returns the new value in {@link BitOffset} or {@link BitOffsetMultiplier}. + * - {@link BitFieldOverflow} determines the behavior of the {@link BitFieldSet} and {@link BitFieldIncrBy} + * subcommands when an overflow or underflow occurs. {@link BitFieldOverflow} does not return a value and + * does not contribute a value to the array response. + * + * @example + * ```typescript + * await client.set("key", "A"); // "A" has binary value 01000001 + * const result = await client.bitfield("key", [new BitFieldSet(new UnsignedEncoding(2), new BitOffset(1), 3), new BitFieldGet(new UnsignedEncoding(2), new BitOffset(1))]); + * console.log(result); // Output: [2, 3] - The old value at offset 1 with an unsigned encoding of 2 was 2. The new value at offset 1 with an unsigned encoding of 2 is 3. + * ``` + */ + public async bitfield( + key: GlideString, + subcommands: BitFieldSubCommands[], + ): Promise<(number | null)[]> { + return this.createWritePromise(createBitField(key, subcommands)); + } + + /** + * Reads the array of bits representing the string that is held at `key` based on the specified `subcommands`. + * + * @see {@link https://valkey.io/commands/bitfield_ro/|valkey.io} for more details. + * @remarks Since Valkey version 6.0.0. + * + * @param key - The key of the string. + * @param subcommands - The {@link BitFieldGet} subcommands to be performed. + * @returns An array of results from the {@link BitFieldGet} subcommands. + * + * @example + * ```typescript + * await client.set("key", "A"); // "A" has binary value 01000001 + * const result = await client.bitfieldReadOnly("key", [new BitFieldGet(new UnsignedEncoding(2), new BitOffset(1))]); + * console.log(result); // Output: [2] - The value at offset 1 with an unsigned encoding of 2 is 2. + * ``` + */ + public async bitfieldReadOnly( + key: GlideString, + subcommands: BitFieldGet[], + ): Promise { + return this.createWritePromise(createBitField(key, subcommands, true)); + } + + /** Retrieve the value associated with `field` in the hash stored at `key`. + * + * @see {@link https://valkey.io/commands/hget/|valkey.io} for details. * * @param key - The key of the hash. * @param field - The field in the hash stored at `key` to retrieve from the database. + * @param options - (Optional) See {@link DecoderOption}. * @returns the value associated with `field`, or null when `field` is not present in the hash or `key` does not exist. * * @example * ```typescript * // Example usage of the hget method on an-existing field - * await client.hset("my_hash", "field"); + * await client.hset("my_hash", {"field": "value"}); * const result = await client.hget("my_hash", "field"); * console.log(result); // Output: "value" * ``` @@ -701,36 +1667,72 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public hget(key: string, field: string): Promise { - return this.createWritePromise(createHGet(key, field)); + public async hget( + key: GlideString, + field: GlideString, + options?: DecoderOption, + ): Promise { + return this.createWritePromise(createHGet(key, field), options); } /** Sets the specified fields to their respective values in the hash stored at `key`. - * See https://valkey.io/commands/hset/ for details. + * + * @see {@link https://valkey.io/commands/hset/|valkey.io} for details. * * @param key - The key of the hash. - * @param fieldValueMap - A field-value map consisting of fields and their corresponding values - * to be set in the hash stored at the specified key. + * @param fieldsAndValues - A list of field names and their values. * @returns The number of fields that were added. * * @example * ```typescript - * // Example usage of the hset method - * const result = await client.hset("my_hash", \{"field": "value", "field2": "value2"\}); + * // Example usage of the hset method using HashDataType as input type + * const result = await client.hset("my_hash", [{"field": "field1", "value": "value1"}, {"field": "field2", "value": "value2"}]); + * console.log(result); // Output: 2 - Indicates that 2 fields were successfully set in the hash "my_hash". + * + * // Example usage of the hset method using Record as input + * const result = await client.hset("my_hash", {"field1": "value", "field2": "value2"}); * console.log(result); // Output: 2 - Indicates that 2 fields were successfully set in the hash "my_hash". * ``` */ - public hset( - key: string, - fieldValueMap: Record, + public async hset( + key: GlideString, + fieldsAndValues: HashDataType | Record, ): Promise { - return this.createWritePromise(createHSet(key, fieldValueMap)); + return this.createWritePromise( + createHSet(key, convertFieldsAndValuesForHset(fieldsAndValues)), + ); + } + + /** + * Returns all field names in the hash stored at `key`. + * + * @see {@link https://valkey.io/commands/hkeys/|valkey.io} for details. + * + * @param key - The key of the hash. + * @param options - (Optional) See {@link DecoderOption}. + * @returns A list of field names for the hash, or an empty list when the key does not exist. + * + * @example + * ```typescript + * // Example usage of the hkeys method: + * await client.hset("my_hash", {"field1": "value1", "field2": "value2", "field3": "value3"}); + * const result = await client.hkeys("my_hash"); + * console.log(result); // Output: ["field1", "field2", "field3"] - Returns all the field names stored in the hash "my_hash". + * ``` + */ + + public async hkeys( + key: GlideString, + options?: DecoderOption, + ): Promise { + return this.createWritePromise(createHKeys(key), options); } /** Sets `field` in the hash stored at `key` to `value`, only if `field` does not yet exist. * If `key` does not exist, a new key holding a hash is created. * If `field` already exists, this operation has no effect. - * See https://valkey.io/commands/hsetnx/ for more details. + * + * @see {@link https://valkey.io/commands/hsetnx/|valkey.io} for more details. * * @param key - The key of the hash. * @param field - The field to set the value for. @@ -751,13 +1753,18 @@ export class BaseClient { * console.log(result); // Output: false - Indicates that the field "field" already existed in the hash "my_hash" and was not set again. * ``` */ - public hsetnx(key: string, field: string, value: string): Promise { + public async hsetnx( + key: GlideString, + field: GlideString, + value: GlideString, + ): Promise { return this.createWritePromise(createHSetNX(key, field, value)); } /** Removes the specified fields from the hash stored at `key`. * Specified fields that do not exist within this hash are ignored. - * See https://valkey.io/commands/hdel/ for details. + * + * @see {@link https://valkey.io/commands/hdel/|valkey.io} for details. * * @param key - The key of the hash. * @param fields - The fields to remove from the hash stored at `key`. @@ -771,15 +1778,20 @@ export class BaseClient { * console.log(result); // Output: 2 - Indicates that two fields were successfully removed from the hash. * ``` */ - public hdel(key: string, fields: string[]): Promise { + public async hdel( + key: GlideString, + fields: GlideString[], + ): Promise { return this.createWritePromise(createHDel(key, fields)); } /** Returns the values associated with the specified fields in the hash stored at `key`. - * See https://valkey.io/commands/hmget/ for details. + * + * @see {@link https://valkey.io/commands/hmget/|valkey.io} for details. * * @param key - The key of the hash. * @param fields - The fields in the hash stored at `key` to retrieve from the database. + * @param options - (Optional) See {@link DecoderOption}. * @returns a list of values associated with the given fields, in the same order as they are requested. * For every field that does not exist in the hash, a null value is returned. * If `key` does not exist, it is treated as an empty hash and it returns a list of null values. @@ -791,12 +1803,17 @@ export class BaseClient { * console.log(result); // Output: ["value1", "value2"] - A list of values associated with the specified fields. * ``` */ - public hmget(key: string, fields: string[]): Promise<(string | null)[]> { - return this.createWritePromise(createHMGet(key, fields)); + public async hmget( + key: GlideString, + fields: GlideString[], + options?: DecoderOption, + ): Promise<(GlideString | null)[]> { + return this.createWritePromise(createHMGet(key, fields), options); } /** Returns if `field` is an existing field in the hash stored at `key`. - * See https://valkey.io/commands/hexists/ for details. + * + * @see {@link https://valkey.io/commands/hexists/|valkey.io} for details. * * @param key - The key of the hash. * @param field - The field to check in the hash stored at `key`. @@ -816,12 +1833,16 @@ export class BaseClient { * console.log(result); // Output: false * ``` */ - public hexists(key: string, field: string): Promise { + public async hexists( + key: GlideString, + field: GlideString, + ): Promise { return this.createWritePromise(createHExists(key, field)); } /** Returns all fields and values of the hash stored at `key`. - * See https://valkey.io/commands/hgetall/ for details. + * + * @see {@link https://valkey.io/commands/hgetall/|valkey.io} for details. * * @param key - The key of the hash. * @returns a list of fields and their values stored in the hash. Every field name in the list is followed by its value. @@ -834,14 +1855,15 @@ export class BaseClient { * console.log(result); // Output: {"field1": "value1", "field2": "value2"} * ``` */ - public hgetall(key: string): Promise> { + public async hgetall(key: GlideString): Promise> { return this.createWritePromise(createHGetAll(key)); } /** Increments the number stored at `field` in the hash stored at `key` by increment. * By using a negative increment value, the value stored at `field` in the hash stored at `key` is decremented. * If `field` or `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/hincrby/ for details. + * + * @see {@link https://valkey.io/commands/hincrby/|valkey.io} for details. * * @param key - The key of the hash. * @param amount - The amount to increment. @@ -855,9 +1877,9 @@ export class BaseClient { * console.log(result); // Output: 5 * ``` */ - public hincrBy( - key: string, - field: string, + public async hincrBy( + key: GlideString, + field: GlideString, amount: number, ): Promise { return this.createWritePromise(createHIncrBy(key, field, amount)); @@ -866,7 +1888,8 @@ export class BaseClient { /** Increment the string representing a floating point number stored at `field` in the hash stored at `key` by increment. * By using a negative increment value, the value stored at `field` in the hash stored at `key` is decremented. * If `field` or `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/hincrbyfloat/ for details. + * + * @see {@link https://valkey.io/commands/hincrbyfloat/|valkey.io} for details. * * @param key - The key of the hash. * @param amount - The amount to increment. @@ -877,19 +1900,20 @@ export class BaseClient { * ```typescript * // Example usage of the hincrbyfloat method to increment the value of a floating point in a hash by a specified amount * const result = await client.hincrbyfloat("my_hash", "field1", 2.5); - * console.log(result); // Output: '2.5' + * console.log(result); // Output: 2.5 * ``` */ - public hincrByFloat( - key: string, - field: string, + public async hincrByFloat( + key: GlideString, + field: GlideString, amount: number, ): Promise { return this.createWritePromise(createHIncrByFloat(key, field, amount)); } /** Returns the number of fields contained in the hash stored at `key`. - * See https://valkey.io/commands/hlen/ for more details. + * + * @see {@link https://valkey.io/commands/hlen/|valkey.io} for more details. * * @param key - The key of the hash. * @returns The number of fields in the hash, or 0 when the key does not exist. @@ -908,14 +1932,17 @@ export class BaseClient { * console.log(result); // Output: 0 * ``` */ - public hlen(key: string): Promise { + public async hlen(key: GlideString): Promise { return this.createWritePromise(createHLen(key)); } /** Returns all values in the hash stored at key. - * See https://valkey.io/commands/hvals/ for more details. + * + * @see {@link https://valkey.io/commands/hvals/|valkey.io} for more details. * * @param key - The key of the hash. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * @returns a list of values in the hash, or an empty list when the key does not exist. * * @example @@ -925,14 +1952,169 @@ export class BaseClient { * console.log(result); // Output: ["value1", "value2", "value3"] - Returns all the values stored in the hash "my_hash". * ``` */ - public hvals(key: string): Promise { - return this.createWritePromise(createHVals(key)); + public async hvals( + key: GlideString, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createHVals(key), { decoder: decoder }); + } + + /** + * Returns the string length of the value associated with `field` in the hash stored at `key`. + * + * @see {@link https://valkey.io/commands/hstrlen/|valkey.io} for details. + * + * @param key - The key of the hash. + * @param field - The field in the hash. + * @returns The string length or `0` if `field` or `key` does not exist. + * + * @example + * ```typescript + * await client.hset("my_hash", {"field": "value"}); + * const result = await client.hstrlen("my_hash", "field"); + * console.log(result); // Output: 5 + * ``` + */ + public async hstrlen( + key: GlideString, + field: GlideString, + ): Promise { + return this.createWritePromise(createHStrlen(key, field)); + } + + /** + * Returns a random field name from the hash value stored at `key`. + * + * @see {@link https://valkey.io/commands/hrandfield/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the hash. + * @param options - (Optional) See {@link DecoderOption}. + * @returns A random field name from the hash stored at `key`, or `null` when + * the key does not exist. + * + * @example + * ```typescript + * console.log(await client.hrandfield("myHash")); // Output: 'field' + * ``` + */ + public async hrandfield( + key: GlideString, + options?: DecoderOption, + ): Promise { + return this.createWritePromise(createHRandField(key), options); + } + + /** + * Iterates incrementally over a hash. + * + * @see {@link https://valkey.io/commands/hscan/|valkey.io} for more details. + * + * @param key - The key of the set. + * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of the search. + * @param options - (Optional) The {@link BaseScanOptions}. + * @returns An array of the `cursor` and the subset of the hash held by `key`. + * The first element is always the `cursor` for the next iteration of results. `"0"` will be the `cursor` + * returned on the last iteration of the hash. The second element is always an array of the subset of the + * hash held in `key`. The array in the second element is always a flattened series of string pairs, + * where the value is at even indices and the value is at odd indices. + * + * @example + * ```typescript + * // Assume "key" contains a hash with multiple members + * let newCursor = "0"; + * let result = []; + * do { + * result = await client.hscan(key1, newCursor, { + * match: "*", + * count: 3, + * }); + * newCursor = result[0]; + * console.log("Cursor: ", newCursor); + * console.log("Members: ", result[1]); + * } while (newCursor !== "0"); + * // The output of the code above is something similar to: + * // Cursor: 31 + * // Members: ['field 79', 'value 79', 'field 20', 'value 20', 'field 115', 'value 115'] + * // Cursor: 39 + * // Members: ['field 63', 'value 63', 'field 293', 'value 293', 'field 162', 'value 162'] + * // Cursor: 0 + * // Members: ['value 55', '55', 'value 24', '24', 'value 90', '90', 'value 113', '113'] + * ``` + */ + public async hscan( + key: string, + cursor: string, + options?: BaseScanOptions, + ): Promise<[string, string[]]> { + return this.createWritePromise(createHScan(key, cursor, options)); + } + + /** + * Retrieves up to `count` random field names from the hash value stored at `key`. + * + * @see {@link https://valkey.io/commands/hrandfield/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the hash. + * @param count - The number of field names to return. + * @param options - (Optional) See {@link DecoderOption}. + * + * If `count` is positive, returns unique elements. If negative, allows for duplicates. + * @returns An `array` of random field names from the hash stored at `key`, + * or an `empty array` when the key does not exist. + * + * @example + * ```typescript + * console.log(await client.hrandfieldCount("myHash", 2)); // Output: ['field1', 'field2'] + * ``` + */ + public async hrandfieldCount( + key: GlideString, + count: number, + options?: DecoderOption, + ): Promise { + return this.createWritePromise(createHRandField(key, count), options); + } + + /** + * Retrieves up to `count` random field names along with their values from the hash + * value stored at `key`. + * + * @see {@link https://valkey.io/commands/hrandfield/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the hash. + * @param count - The number of field names to return. + * @param options - (Optional) See {@link DecoderOption}. + * + * If `count` is positive, returns unique elements. If negative, allows for duplicates. + * @returns A 2D `array` of `[fieldName, value]` `arrays`, where `fieldName` is a random + * field name from the hash and `value` is the associated value of the field name. + * If the hash does not exist or is empty, the response will be an empty `array`. + * + * @example + * ```typescript + * const result = await client.hrandfieldCountWithValues("myHash", 2); + * console.log(result); // Output: [['field1', 'value1'], ['field2', 'value2']] + * ``` + */ + public async hrandfieldWithValues( + key: GlideString, + count: number, + options?: DecoderOption, + ): Promise<[GlideString, GlideString][]> { + return this.createWritePromise( + createHRandField(key, count, true), + options, + ); } /** Inserts all the specified values at the head of the list stored at `key`. * `elements` are inserted one after the other to the head of the list, from the leftmost element to the rightmost element. * If `key` does not exist, it is created as empty list before performing the push operations. - * See https://valkey.io/commands/lpush/ for details. + * + * @see {@link https://valkey.io/commands/lpush/|valkey.io} for details. * * @param key - The key of the list. * @param elements - The elements to insert at the head of the list stored at `key`. @@ -952,15 +2134,43 @@ export class BaseClient { * console.log(result); // Output: 1 - Indicates that a new list was created with one element * ``` */ - public lpush(key: string, elements: string[]): Promise { + public async lpush( + key: GlideString, + elements: GlideString[], + ): Promise { return this.createWritePromise(createLPush(key, elements)); } + /** + * Inserts specified values at the head of the `list`, only if `key` already + * exists and holds a list. + * + * @see {@link https://valkey.io/commands/lpushx/|valkey.io} for details. + * + * @param key - The key of the list. + * @param elements - The elements to insert at the head of the list stored at `key`. + * @returns The length of the list after the push operation. + * @example + * ```typescript + * const listLength = await client.lpushx("my_list", ["value1", "value2"]); + * console.log(result); // Output: 2 - Indicates that the list has two elements. + * ``` + */ + public async lpushx( + key: GlideString, + elements: GlideString[], + ): Promise { + return this.createWritePromise(createLPushX(key, elements)); + } + /** Removes and returns the first elements of the list stored at `key`. * The command pops a single element from the beginning of the list. - * See https://valkey.io/commands/lpop/ for details. + * + * @see {@link https://valkey.io/commands/lpop/|valkey.io} for details. * * @param key - The key of the list. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * @returns The value of the first element. * If `key` does not exist null will be returned. * @@ -978,15 +2188,21 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public lpop(key: string): Promise { - return this.createWritePromise(createLPop(key)); + public async lpop( + key: GlideString, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createLPop(key), { decoder: decoder }); } /** Removes and returns up to `count` elements of the list stored at `key`, depending on the list's length. - * See https://valkey.io/commands/lpop/ for details. + * + * @see {@link https://valkey.io/commands/lpop/|valkey.io} for details. * * @param key - The key of the list. * @param count - The count of the elements to pop from the list. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * @returns A list of the popped elements will be returned depending on the list's length. * If `key` does not exist null will be returned. * @@ -1004,19 +2220,28 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public lpopCount(key: string, count: number): Promise { - return this.createWritePromise(createLPop(key, count)); + public async lpopCount( + key: GlideString, + count: number, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createLPop(key, count), { + decoder: decoder, + }); } /** Returns the specified elements of the list stored at `key`. * The offsets `start` and `end` are zero-based indexes, with 0 being the first element of the list, 1 being the next element and so on. * These offsets can also be negative numbers indicating offsets starting at the end of the list, * with -1 being the last element of the list, -2 being the penultimate, and so on. - * See https://valkey.io/commands/lrange/ for details. + * + * @see {@link https://valkey.io/commands/lrange/|valkey.io} for details. * * @param key - The key of the list. * @param start - The starting point of the range. * @param end - The end of the range. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * @returns list of elements in the specified range. * If `start` exceeds the end of the list, or if `start` is greater than `end`, an empty list will be returned. * If `end` exceeds the actual end of the list, the range will stop at the actual end of the list. @@ -1043,12 +2268,20 @@ export class BaseClient { * console.log(result); // Output: [] * ``` */ - public lrange(key: string, start: number, end: number): Promise { - return this.createWritePromise(createLRange(key, start, end)); + public async lrange( + key: GlideString, + start: number, + end: number, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createLRange(key, start, end), { + decoder: decoder, + }); } /** Returns the length of the list stored at `key`. - * See https://valkey.io/commands/llen/ for details. + * + * @see {@link https://valkey.io/commands/llen/|valkey.io} for details. * * @param key - The key of the list. * @returns the length of the list at `key`. @@ -1061,17 +2294,109 @@ export class BaseClient { * console.log(result); // Output: 3 - Indicates that there are 3 elements in the list. * ``` */ - public llen(key: string): Promise { + public async llen(key: GlideString): Promise { return this.createWritePromise(createLLen(key)); } + /** + * Atomically pops and removes the left/right-most element to the list stored at `source` + * depending on `whereTo`, and pushes the element at the first/last element of the list + * stored at `destination` depending on `whereFrom`, see {@link ListDirection}. + * + * @see {@link https://valkey.io/commands/lmove/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param source - The key to the source list. + * @param destination - The key to the destination list. + * @param whereFrom - The {@link ListDirection} to remove the element from. + * @param whereTo - The {@link ListDirection} to add the element to. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. + * @returns The popped element, or `null` if `source` does not exist. + * + * @example + * ```typescript + * await client.lpush("testKey1", ["two", "one"]); + * await client.lpush("testKey2", ["four", "three"]); + * + * const result1 = await client.lmove("testKey1", "testKey2", ListDirection.LEFT, ListDirection.LEFT); + * console.log(result1); // Output: "one". + * + * const updated_array_key1 = await client.lrange("testKey1", 0, -1); + * console.log(updated_array); // Output: "two". + * + * const updated_array_key2 = await client.lrange("testKey2", 0, -1); + * console.log(updated_array_key2); // Output: ["one", "three", "four"]. + * ``` + */ + public async lmove( + source: GlideString, + destination: GlideString, + whereFrom: ListDirection, + whereTo: ListDirection, + decoder?: Decoder, + ): Promise { + return this.createWritePromise( + createLMove(source, destination, whereFrom, whereTo), + { decoder: decoder }, + ); + } + + /** + * Blocks the connection until it pops atomically and removes the left/right-most element to the + * list stored at `source` depending on `whereFrom`, and pushes the element at the first/last element + * of the list stored at `destination` depending on `whereTo`. + * `BLMOVE` is the blocking variant of {@link lmove}. + * + * @see {@link https://valkey.io/commands/blmove/|valkey.io} for details. + * @remarks When in cluster mode, both `source` and `destination` must map to the same hash slot. + * @remarks `BLMOVE` is a client blocking command, see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands|Valkey Glide Wiki} for more details and best practices. + * @remarks Since Valkey version 6.2.0. + * + * @param source - The key to the source list. + * @param destination - The key to the destination list. + * @param whereFrom - The {@link ListDirection} to remove the element from. + * @param whereTo - The {@link ListDirection} to add the element to. + * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of `0` will block indefinitely. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. + * @returns The popped element, or `null` if `source` does not exist or if the operation timed-out. + * + * @example + * ```typescript + * await client.lpush("testKey1", ["two", "one"]); + * await client.lpush("testKey2", ["four", "three"]); + * const result = await client.blmove("testKey1", "testKey2", ListDirection.LEFT, ListDirection.LEFT, 0.1); + * console.log(result); // Output: "one" + * + * const result2 = await client.lrange("testKey1", 0, -1); + * console.log(result2); // Output: "two" + * + * const updated_array2 = await client.lrange("testKey2", 0, -1); + * console.log(updated_array2); // Output: ["one", "three", "four"] + * ``` + */ + public async blmove( + source: GlideString, + destination: GlideString, + whereFrom: ListDirection, + whereTo: ListDirection, + timeout: number, + decoder?: Decoder, + ): Promise { + return this.createWritePromise( + createBLMove(source, destination, whereFrom, whereTo, timeout), + { decoder: decoder }, + ); + } + /** * Sets the list element at `index` to `element`. * The index is zero-based, so `0` means the first element, `1` the second element and so on. * Negative indices can be used to designate elements starting at the tail of * the list. Here, `-1` means the last element, `-2` means the penultimate and so forth. * - * See https://valkey.io/commands/lset/ for details. + * @see {@link https://valkey.io/commands/lset/|valkey.io} for details. * * @param key - The key of the list. * @param index - The index of the element in the list to be set. @@ -1085,15 +2410,22 @@ export class BaseClient { * console.log(response); // Output: 'OK' - Indicates that the second index of the list has been set to "two". * ``` */ - public lset(key: string, index: number, element: string): Promise<"OK"> { - return this.createWritePromise(createLSet(key, index, element)); + public async lset( + key: GlideString, + index: number, + element: GlideString, + ): Promise<"OK"> { + return this.createWritePromise(createLSet(key, index, element), { + decoder: Decoder.String, + }); } /** Trim an existing list so that it will contain only the specified range of elements specified. * The offsets `start` and `end` are zero-based indexes, with 0 being the first element of the list, 1 being the next element and so on. * These offsets can also be negative numbers indicating offsets starting at the end of the list, * with -1 being the last element of the list, -2 being the penultimate, and so on. - * See https://valkey.io/commands/ltrim/ for details. + * + * @see {@link https://valkey.io/commands/ltrim/|valkey.io} for details. * * @param key - The key of the list. * @param start - The starting point of the range. @@ -1110,8 +2442,14 @@ export class BaseClient { * console.log(result); // Output: 'OK' - Indicates that the list has been trimmed to contain elements from 0 to 1. * ``` */ - public ltrim(key: string, start: number, end: number): Promise<"OK"> { - return this.createWritePromise(createLTrim(key, start, end)); + public async ltrim( + key: GlideString, + start: number, + end: number, + ): Promise<"OK"> { + return this.createWritePromise(createLTrim(key, start, end), { + decoder: Decoder.String, + }); } /** Removes the first `count` occurrences of elements equal to `element` from the list stored at `key`. @@ -1132,14 +2470,19 @@ export class BaseClient { * console.log(result); // Output: 2 - Removes the first 2 occurrences of "value" in the list. * ``` */ - public lrem(key: string, count: number, element: string): Promise { + public async lrem( + key: GlideString, + count: number, + element: GlideString, + ): Promise { return this.createWritePromise(createLRem(key, count, element)); } /** Inserts all the specified values at the tail of the list stored at `key`. * `elements` are inserted one after the other to the tail of the list, from the leftmost element to the rightmost element. * If `key` does not exist, it is created as empty list before performing the push operations. - * See https://valkey.io/commands/rpush/ for details. + * + * @see {@link https://valkey.io/commands/rpush/|valkey.io} for details. * * @param key - The key of the list. * @param elements - The elements to insert at the tail of the list stored at `key`. @@ -1159,15 +2502,43 @@ export class BaseClient { * console.log(result); // Output: 1 * ``` */ - public rpush(key: string, elements: string[]): Promise { + public async rpush( + key: GlideString, + elements: GlideString[], + ): Promise { return this.createWritePromise(createRPush(key, elements)); } + /** + * Inserts specified values at the tail of the `list`, only if `key` already + * exists and holds a list. + * + * @see {@link https://valkey.io/commands/rpushx/|valkey.io} for details. + * + * @param key - The key of the list. + * @param elements - The elements to insert at the tail of the list stored at `key`. + * @returns The length of the list after the push operation. + * @example + * ```typescript + * const result = await client.rpushx("my_list", ["value1", "value2"]); + * console.log(result); // Output: 2 - Indicates that the list has two elements. + * ``` + * */ + public async rpushx( + key: GlideString, + elements: GlideString[], + ): Promise { + return this.createWritePromise(createRPushX(key, elements)); + } + /** Removes and returns the last elements of the list stored at `key`. * The command pops a single element from the end of the list. - * See https://valkey.io/commands/rpop/ for details. + * + * @see {@link https://valkey.io/commands/rpop/|valkey.io} for details. * * @param key - The key of the list. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * @returns The value of the last element. * If `key` does not exist null will be returned. * @@ -1185,15 +2556,21 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public rpop(key: string): Promise { - return this.createWritePromise(createRPop(key)); + public async rpop( + key: GlideString, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createRPop(key), { decoder: decoder }); } /** Removes and returns up to `count` elements from the list stored at `key`, depending on the list's length. - * See https://valkey.io/commands/rpop/ for details. + * + * @see {@link https://valkey.io/commands/rpop/|valkey.io} for details. * * @param key - The key of the list. * @param count - The count of the elements to pop from the list. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * @returns A list of popped elements will be returned depending on the list's length. * If `key` does not exist null will be returned. * @@ -1211,13 +2588,20 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public rpopCount(key: string, count: number): Promise { - return this.createWritePromise(createRPop(key, count)); + public async rpopCount( + key: GlideString, + count: number, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createRPop(key, count), { + decoder: decoder, + }); } /** Adds the specified members to the set stored at `key`. Specified members that are already a member of this set are ignored. * If `key` does not exist, a new set is created before adding `members`. - * See https://valkey.io/commands/sadd/ for details. + * + * @see {@link https://valkey.io/commands/sadd/|valkey.io} for details. * * @param key - The key to store the members to its set. * @param members - A list of members to add to the set stored at `key`. @@ -1230,12 +2614,16 @@ export class BaseClient { * console.log(result); // Output: 2 * ``` */ - public sadd(key: string, members: string[]): Promise { + public async sadd( + key: GlideString, + members: GlideString[], + ): Promise { return this.createWritePromise(createSAdd(key, members)); } /** Removes the specified members from the set stored at `key`. Specified members that are not a member of this set are ignored. - * See https://valkey.io/commands/srem/ for details. + * + * @see {@link https://valkey.io/commands/srem/|valkey.io} for details. * * @param key - The key to remove the members from its set. * @param members - A list of members to remove from the set stored at `key`. @@ -1249,14 +2637,66 @@ export class BaseClient { * console.log(result); // Output: 2 * ``` */ - public srem(key: string, members: string[]): Promise { + public async srem( + key: GlideString, + members: GlideString[], + ): Promise { return this.createWritePromise(createSRem(key, members)); } + /** + * Iterates incrementally over a set. + * + * @see {@link https://valkey.io/commands/sscan} for details. + * + * @param key - The key of the set. + * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of the search. + * @param options - (Optional) See {@link BaseScanOptions} and {@link DecoderOption}. + * @returns An array of the cursor and the subset of the set held by `key`. The first element is always the `cursor` and for the next iteration of results. + * The `cursor` will be `"0"` on the last iteration of the set. The second element is always an array of the subset of the set held in `key`. + * + * @example + * ```typescript + * // Assume key contains a set with 200 members + * let newCursor = "0"; + * let result = []; + * + * do { + * result = await client.sscan(key1, newCursor, { + * match: "*", + * count: 5, + * }); + * newCursor = result[0]; + * console.log("Cursor: ", newCursor); + * console.log("Members: ", result[1]); + * } while (newCursor !== "0"); + * + * // The output of the code above is something similar to: + * // Cursor: 8, Match: "f*" + * // Members: ['field', 'fur', 'fun', 'fame'] + * // Cursor: 20, Count: 3 + * // Members: ['1', '2', '3', '4', '5', '6'] + * // Cursor: 0 + * // Members: ['1', '2', '3', '4', '5', '6'] + * ``` + */ + public async sscan( + key: GlideString, + cursor: GlideString, + options?: BaseScanOptions & DecoderOption, + ): Promise<[GlideString, GlideString[]]> { + return this.createWritePromise( + createSScan(key, cursor, options), + options, + ); + } + /** Returns all the members of the set value stored at `key`. - * See https://valkey.io/commands/smembers/ for details. + * + * @see {@link https://valkey.io/commands/smembers/|valkey.io} for details. * * @param key - The key to return its members. + * @param options - (Optional) See {@link DecoderOption}. * @returns A `Set` containing all members of the set. * If `key` does not exist, it is treated as an empty set and this command returns an empty `Set`. * @@ -1267,16 +2707,20 @@ export class BaseClient { * console.log(result); // Output: Set {'member1', 'member2', 'member3'} * ``` */ - public smembers(key: string): Promise> { - return this.createWritePromise(createSMembers(key)).then( - (smembes) => new Set(smembes), - ); + public async smembers( + key: GlideString, + options?: DecoderOption, + ): Promise> { + return this.createWritePromise( + createSMembers(key), + options, + ).then((smembers) => new Set(smembers)); } /** Moves `member` from the set at `source` to the set at `destination`, removing it from the source set. * Creates a new destination set if needed. The operation is atomic. - * See https://valkey.io/commands/smove for more details. * + * @see {@link https://valkey.io/commands/smove/|valkey.io} for more details. * @remarks When in cluster mode, `source` and `destination` must map to the same hash slot. * * @param source - The key of the set to remove the element from. @@ -1290,10 +2734,10 @@ export class BaseClient { * console.log(result); // Output: true - "member1" was moved from "set1" to "set2". * ``` */ - public smove( - source: string, - destination: string, - member: string, + public async smove( + source: GlideString, + destination: GlideString, + member: GlideString, ): Promise { return this.createWritePromise( createSMove(source, destination, member), @@ -1301,7 +2745,8 @@ export class BaseClient { } /** Returns the set cardinality (number of elements) of the set stored at `key`. - * See https://valkey.io/commands/scard/ for details. + * + * @see {@link https://valkey.io/commands/scard/|valkey.io} for details. * * @param key - The key to return the number of its members. * @returns The cardinality (number of elements) of the set, or 0 if key does not exist. @@ -1313,15 +2758,17 @@ export class BaseClient { * console.log(result); // Output: 3 * ``` */ - public scard(key: string): Promise { + public async scard(key: GlideString): Promise { return this.createWritePromise(createSCard(key)); } /** Gets the intersection of all the given sets. - * See https://valkey.io/docs/latest/commands/sinter/ for more details. * + * @see {@link https://valkey.io/docs/latest/commands/sinter/|valkey.io} for more details. * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * * @param keys - The `keys` of the sets to get the intersection. + * @param options - (Optional) See {@link DecoderOption}. * @returns - A set of members which are present in all given sets. * If one or more sets do not exist, an empty set will be returned. * @@ -1339,18 +2786,51 @@ export class BaseClient { * console.log(result); // Output: Set {} - An empty set is returned since the key does not exist. * ``` */ - public sinter(keys: string[]): Promise> { - return this.createWritePromise(createSInter(keys)).then( - (sinter) => new Set(sinter), - ); + public async sinter( + keys: GlideString[], + options?: DecoderOption, + ): Promise> { + return this.createWritePromise( + createSInter(keys), + options, + ).then((sinter) => new Set(sinter)); } /** - * Stores the members of the intersection of all given sets specified by `keys` into a new set at `destination`. + * Gets the cardinality of the intersection of all the given sets. + * + * @see {@link https://valkey.io/commands/sintercard/|valkey.io} for more details. + * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks Since Valkey version 7.0.0. + * + * @param keys - The keys of the sets. + * @param limit - The limit for the intersection cardinality value. If not specified, or set to `0`, no limit is used. + * @returns The cardinality of the intersection result. If one or more sets do not exist, `0` is returned. + * + * @example + * ```typescript + * await client.sadd("set1", ["a", "b", "c"]); + * await client.sadd("set2", ["b", "c", "d"]); + * const result1 = await client.sintercard(["set1", "set2"]); + * console.log(result1); // Output: 2 - The intersection of "set1" and "set2" contains 2 elements: "b" and "c". * - * See https://valkey.io/commands/sinterstore/ for more details. + * const result2 = await client.sintercard(["set1", "set2"], 1); + * console.log(result2); // Output: 1 - The computation stops early as the intersection cardinality reaches the limit of 1. + * ``` + */ + public async sintercard( + keys: GlideString[], + limit?: number, + ): Promise { + return this.createWritePromise(createSInterCard(keys, limit)); + } + + /** + * Stores the members of the intersection of all given sets specified by `keys` into a new set at `destination`. * + * @see {@link https://valkey.io/commands/sinterstore/|valkey.io} for more details. * @remarks When in cluster mode, `destination` and all `keys` must map to the same hash slot. + * * @param destination - The key of the destination set. * @param keys - The keys from which to retrieve the set members. * @returns The number of elements in the resulting set. @@ -1361,17 +2841,21 @@ export class BaseClient { * console.log(result); // Output: 2 - Two elements were stored at "my_set", and those elements are the intersection of "set1" and "set2". * ``` */ - public sinterstore(destination: string, keys: string[]): Promise { + public async sinterstore( + destination: GlideString, + keys: GlideString[], + ): Promise { return this.createWritePromise(createSInterStore(destination, keys)); } /** * Computes the difference between the first set and all the successive sets in `keys`. * - * See https://valkey.io/commands/sdiff/ for more details. - * + * @see {@link https://valkey.io/commands/sdiff/|valkey.io} for more details. * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * * @param keys - The keys of the sets to diff. + * @param options - (Optional) See {@link DecoderOption}. * @returns A `Set` of elements representing the difference between the sets. * If a key in `keys` does not exist, it is treated as an empty set. * @@ -1383,18 +2867,22 @@ export class BaseClient { * console.log(result); // Output: Set {"member1"} - "member2" is in "set1" but not "set2" * ``` */ - public sdiff(keys: string[]): Promise> { - return this.createWritePromise(createSDiff(keys)).then( - (sdiff) => new Set(sdiff), - ); + public async sdiff( + keys: GlideString[], + options?: DecoderOption, + ): Promise> { + return this.createWritePromise( + createSDiff(keys), + options, + ).then((sdiff) => new Set(sdiff)); } /** * Stores the difference between the first set and all the successive sets in `keys` into a new set at `destination`. * - * See https://valkey.io/commands/sdiffstore/ for more details. - * + * @see {@link https://valkey.io/commands/sdiffstore/|valkey.io} for more details. * @remarks When in cluster mode, `destination` and all `keys` must map to the same hash slot. + * * @param destination - The key of the destination set. * @param keys - The keys of the sets to diff. * @returns The number of elements in the resulting set. @@ -1407,17 +2895,21 @@ export class BaseClient { * console.log(result); // Output: 1 - One member was stored in "set3", and that member is the diff between "set1" and "set2". * ``` */ - public sdiffstore(destination: string, keys: string[]): Promise { + public async sdiffstore( + destination: GlideString, + keys: GlideString[], + ): Promise { return this.createWritePromise(createSDiffStore(destination, keys)); } /** * Gets the union of all the given sets. * - * See https://valkey.io/commands/sunion/ for more details. - * + * @see {@link https://valkey.io/commands/sunion/|valkey.io} for more details. * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * * @param keys - The keys of the sets. + * @param options - (Optional) See {@link DecoderOption}. * @returns A `Set` of members which are present in at least one of the given sets. * If none of the sets exist, an empty `Set` will be returned. * @@ -1432,19 +2924,22 @@ export class BaseClient { * console.log(result2); // Output: Set {'member1', 'member2'} * ``` */ - public sunion(keys: string[]): Promise> { - return this.createWritePromise(createSUnion(keys)).then( - (sunion) => new Set(sunion), - ); + public async sunion( + keys: GlideString[], + options?: DecoderOption, + ): Promise> { + return this.createWritePromise(createSUnion(keys), { + decoder: options?.decoder, + }).then((sunion) => new Set(sunion)); } /** * Stores the members of the union of all given sets specified by `keys` into a new set * at `destination`. * - * See https://valkey.io/commands/sunionstore/ for details. - * + * @see {@link https://valkey.io/commands/sunionstore/|valkey.io} for details. * @remarks When in cluster mode, `destination` and all `keys` must map to the same hash slot. + * * @param destination - The key of the destination set. * @param keys - The keys from which to retrieve the set members. * @returns The number of elements in the resulting set. @@ -1455,12 +2950,16 @@ export class BaseClient { * console.log(length); // Output: 2 - Two elements were stored in "mySet", and those two members are the union of "set1" and "set2". * ``` */ - public sunionstore(destination: string, keys: string[]): Promise { + public async sunionstore( + destination: GlideString, + keys: GlideString[], + ): Promise { return this.createWritePromise(createSUnionStore(destination, keys)); } /** Returns if `member` is a member of the set stored at `key`. - * See https://valkey.io/commands/sismember/ for more details. + * + * @see {@link https://valkey.io/commands/sismember/|valkey.io} for more details. * * @param key - The key of the set. * @param member - The member to check for existence in the set. @@ -1481,15 +2980,44 @@ export class BaseClient { * console.log(result); // Output: false - Indicates that "non_existing_member" does not exist in the set "my_set". * ``` */ - public sismember(key: string, member: string): Promise { + public async sismember( + key: GlideString, + member: GlideString, + ): Promise { return this.createWritePromise(createSIsMember(key, member)); } + /** + * Checks whether each member is contained in the members of the set stored at `key`. + * + * @see {@link https://valkey.io/commands/smismember/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the set to check. + * @param members - A list of members to check for existence in the set. + * @returns An `array` of `boolean` values, each indicating if the respective member exists in the set. + * + * @example + * ```typescript + * await client.sadd("set1", ["a", "b", "c"]); + * const result = await client.smismember("set1", ["b", "c", "d"]); + * console.log(result); // Output: [true, true, false] - "b" and "c" are members of "set1", but "d" is not. + * ``` + */ + public async smismember( + key: GlideString, + members: GlideString[], + ): Promise { + return this.createWritePromise(createSMIsMember(key, members)); + } + /** Removes and returns one random member from the set value store at `key`. - * See https://valkey.io/commands/spop/ for details. - * To pop multiple members, see `spopCount`. + * To pop multiple members, see {@link spopCount}. + * + * @see {@link https://valkey.io/commands/spop/|valkey.io} for details. * * @param key - The key of the set. + * @param options - (Optional) See {@link DecoderOption}. * @returns the value of the popped member. * If `key` does not exist, null will be returned. * @@ -1507,15 +3035,20 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public spop(key: string): Promise { - return this.createWritePromise(createSPop(key)); + public async spop( + key: GlideString, + options?: DecoderOption, + ): Promise { + return this.createWritePromise(createSPop(key), options); } /** Removes and returns up to `count` random members from the set value store at `key`, depending on the set's length. - * See https://valkey.io/commands/spop/ for details. + * + * @see {@link https://valkey.io/commands/spop/|valkey.io} for details. * * @param key - The key of the set. * @param count - The count of the elements to pop from the set. + * @param options - (Optional) See {@link DecoderOption}. * @returns A `Set` containing the popped elements, depending on the set's length. * If `key` does not exist, an empty `Set` will be returned. * @@ -1533,14 +3066,85 @@ export class BaseClient { * console.log(result); // Output: Set {} - An empty set is returned since the key does not exist. * ``` */ - public async spopCount(key: string, count: number): Promise> { - return this.createWritePromise(createSPop(key, count)).then( - (spop) => new Set(spop), - ); + public async spopCount( + key: GlideString, + count: number, + options?: DecoderOption, + ): Promise> { + return this.createWritePromise( + createSPop(key, count), + options, + ).then((spop) => new Set(spop)); + } + + /** + * Returns a random element from the set value stored at `key`. + * + * @see {@link https://valkey.io/commands/srandmember/|valkey.io} for more details. + * + * @param key - The key from which to retrieve the set member. + * @param options - (Optional) See {@link DecoderOption}. + * @returns A random element from the set, or null if `key` does not exist. + * + * @example + * ```typescript + * // Example usage of srandmember method to return a random member from a set + * const result = await client.srandmember("my_set"); + * console.log(result); // Output: 'member1' - A random member of "my_set". + * ``` + * + * @example + * ```typescript + * // Example usage of srandmember method with non-existing key + * const result = await client.srandmember("non_existing_set"); + * console.log(result); // Output: null + * ``` + */ + public async srandmember( + key: GlideString, + options?: DecoderOption, + ): Promise { + return this.createWritePromise(createSRandMember(key), options); + } + + /** + * Returns one or more random elements from the set value stored at `key`. + * + * @see {@link https://valkey.io/commands/srandmember/|valkey.io} for more details. + * + * @param key - The key of the sorted set. + * @param count - The number of members to return. + * If `count` is positive, returns unique members. + * If `count` is negative, allows for duplicates members. + * @param options - (Optional) See {@link DecoderOption}. + * @returns a list of members from the set. If the set does not exist or is empty, an empty list will be returned. + * + * @example + * ```typescript + * // Example usage of srandmemberCount method to return multiple random members from a set + * const result = await client.srandmemberCount("my_set", -3); + * console.log(result); // Output: ['member1', 'member1', 'member2'] - Random members of "my_set". + * ``` + * + * @example + * ```typescript + * // Example usage of srandmemberCount method with non-existing key + * const result = await client.srandmemberCount("non_existing_set", 3); + * console.log(result); // Output: [] - An empty list since the key does not exist. + * ``` + */ + public async srandmemberCount( + key: GlideString, + count: number, + options?: DecoderOption, + ): Promise { + return this.createWritePromise(createSRandMember(key, count), options); } - /** Returns the number of keys in `keys` that exist in the database. - * See https://valkey.io/commands/exists/ for details. + /** + * Returns the number of keys in `keys` that exist in the database. + * + * @see {@link https://valkey.io/commands/exists/|valkey.io} for details. * * @param keys - The keys list to check. * @returns The number of keys that exist. If the same existing key is mentioned in `keys` multiple times, @@ -1553,14 +3157,16 @@ export class BaseClient { * console.log(result); // Output: 3 - Indicates that all three keys exist in the database. * ``` */ - public exists(keys: string[]): Promise { + public async exists(keys: GlideString[]): Promise { return this.createWritePromise(createExists(keys)); } - /** Removes the specified keys. A key is ignored if it does not exist. - * This command, similar to DEL, removes specified keys and ignores non-existent ones. - * However, this command does not block the server, while [DEL](https://valkey.io/commands/del) does. - * See https://valkey.io/commands/unlink/ for details. + /** + * Removes the specified keys. A key is ignored if it does not exist. + * This command, similar to {@link del}, removes specified keys and ignores non-existent ones. + * However, this command does not block the server, while {@link https://valkey.io/commands/del|`DEL`} does. + * + * @see {@link https://valkey.io/commands/unlink/|valkey.io} for details. * * @param keys - The keys we wanted to unlink. * @returns The number of keys that were unlinked. @@ -1572,19 +3178,21 @@ export class BaseClient { * console.log(result); // Output: 3 - Indicates that all three keys were unlinked from the database. * ``` */ - public unlink(keys: string[]): Promise { + public async unlink(keys: GlideString[]): Promise { return this.createWritePromise(createUnlink(keys)); } - /** Sets a timeout on `key` in seconds. After the timeout has expired, the key will automatically be deleted. + /** + * Sets a timeout on `key` in seconds. After the timeout has expired, the key will automatically be deleted. * If `key` already has an existing expire set, the time to live is updated to the new value. * If `seconds` is non-positive number, the key will be deleted rather than expired. * The timeout will only be cleared by commands that delete or overwrite the contents of `key`. - * See https://valkey.io/commands/expire/ for details. + * + * @see {@link https://valkey.io/commands/expire/|valkey.io} for details. * * @param key - The key to set timeout on it. * @param seconds - The timeout in seconds. - * @param option - The expire option. + * @param option - (Optional) The expire option - see {@link ExpireOptions}. * @returns `true` if the timeout was set. `false` if the timeout was not set. e.g. key doesn't exist, * or operation skipped due to the provided arguments. * @@ -1602,23 +3210,25 @@ export class BaseClient { * console.log(result); // Output: false - Indicates that "my_key" has an existing expiry. * ``` */ - public expire( - key: string, + public async expire( + key: GlideString, seconds: number, option?: ExpireOptions, ): Promise { return this.createWritePromise(createExpire(key, seconds, option)); } - /** Sets a timeout on `key`. It takes an absolute Unix timestamp (seconds since January 1, 1970) instead of specifying the number of seconds. + /** + * Sets a timeout on `key`. It takes an absolute Unix timestamp (seconds since January 1, 1970) instead of specifying the number of seconds. * A timestamp in the past will delete the key immediately. After the timeout has expired, the key will automatically be deleted. * If `key` already has an existing expire set, the time to live is updated to the new value. * The timeout will only be cleared by commands that delete or overwrite the contents of `key`. - * See https://valkey.io/commands/expireat/ for details. + * + * @see {@link https://valkey.io/commands/expireat/|valkey.io} for details. * * @param key - The key to set timeout on it. * @param unixSeconds - The timeout in an absolute Unix timestamp. - * @param option - The expire option. + * @param option - (Optional) The expire option - see {@link ExpireOptions}. * @returns `true` if the timeout was set. `false` if the timeout was not set. e.g. key doesn't exist, * or operation skipped due to the provided arguments. * @@ -1629,8 +3239,8 @@ export class BaseClient { * console.log(result); // Output: true - Indicates that the expiration time for "my_key" was successfully set. * ``` */ - public expireAt( - key: string, + public async expireAt( + key: GlideString, unixSeconds: number, option?: ExpireOptions, ): Promise { @@ -1639,15 +3249,45 @@ export class BaseClient { ); } - /** Sets a timeout on `key` in milliseconds. After the timeout has expired, the key will automatically be deleted. + /** + * Returns the absolute Unix timestamp (since January 1, 1970) at which the given `key` will expire, in seconds. + * To get the expiration with millisecond precision, use {@link pexpiretime}. + * + * @see {@link https://valkey.io/commands/expiretime/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param key - The `key` to determine the expiration value of. + * @returns The expiration Unix timestamp in seconds, `-2` if `key` does not exist or `-1` if `key` exists but has no associated expire. + * + * @example + * ```typescript + * const result1 = await client.expiretime("myKey"); + * console.log(result1); // Output: -2 - myKey doesn't exist. + * + * const result2 = await client.set(myKey, "value"); + * const result3 = await client.expireTime(myKey); + * console.log(result2); // Output: -1 - myKey has no associated expiration. + * + * client.expire(myKey, 60); + * const result3 = await client.expireTime(myKey); + * console.log(result3); // Output: 123456 - the Unix timestamp (in seconds) when "myKey" will expire. + * ``` + */ + public async expiretime(key: GlideString): Promise { + return this.createWritePromise(createExpireTime(key)); + } + + /** + * Sets a timeout on `key` in milliseconds. After the timeout has expired, the key will automatically be deleted. * If `key` already has an existing expire set, the time to live is updated to the new value. * If `milliseconds` is non-positive number, the key will be deleted rather than expired. * The timeout will only be cleared by commands that delete or overwrite the contents of `key`. - * See https://valkey.io/commands/pexpire/ for details. + * + * @see {@link https://valkey.io/commands/pexpire/|valkey.io} for details. * * @param key - The key to set timeout on it. * @param milliseconds - The timeout in milliseconds. - * @param option - The expire option. + * @param option - (Optional) The expire option - see {@link ExpireOptions}. * @returns `true` if the timeout was set. `false` if the timeout was not set. e.g. key doesn't exist, * or operation skipped due to the provided arguments. * @@ -1658,8 +3298,8 @@ export class BaseClient { * console.log(result); // Output: true - Indicates that a timeout of 60,000 milliseconds has been set for "my_key". * ``` */ - public pexpire( - key: string, + public async pexpire( + key: GlideString, milliseconds: number, option?: ExpireOptions, ): Promise { @@ -1668,15 +3308,17 @@ export class BaseClient { ); } - /** Sets a timeout on `key`. It takes an absolute Unix timestamp (milliseconds since January 1, 1970) instead of specifying the number of milliseconds. + /** + * Sets a timeout on `key`. It takes an absolute Unix timestamp (milliseconds since January 1, 1970) instead of specifying the number of milliseconds. * A timestamp in the past will delete the key immediately. After the timeout has expired, the key will automatically be deleted. * If `key` already has an existing expire set, the time to live is updated to the new value. * The timeout will only be cleared by commands that delete or overwrite the contents of `key`. - * See https://valkey.io/commands/pexpireat/ for details. + * + * @see {@link https://valkey.io/commands/pexpireat/|valkey.io} for details. * * @param key - The key to set timeout on it. * @param unixMilliseconds - The timeout in an absolute Unix timestamp. - * @param option - The expire option. + * @param option - (Optional) The expire option - see {@link ExpireOptions}. * @returns `true` if the timeout was set. `false` if the timeout was not set. e.g. key doesn't exist, * or operation skipped due to the provided arguments. * @@ -1687,8 +3329,8 @@ export class BaseClient { * console.log(result); // Output: true - Indicates that the expiration time for "my_key" was successfully set. * ``` */ - public pexpireAt( - key: string, + public async pexpireAt( + key: GlideString, unixMilliseconds: number, option?: ExpireOptions, ): Promise { @@ -1697,11 +3339,40 @@ export class BaseClient { ); } - /** Returns the remaining time to live of `key` that has a timeout. - * See https://valkey.io/commands/ttl/ for details. + /** + * Returns the absolute Unix timestamp (since January 1, 1970) at which the given `key` will expire, in milliseconds. + * + * @see {@link https://valkey.io/commands/pexpiretime/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param key - The `key` to determine the expiration value of. + * @returns The expiration Unix timestamp in seconds, `-2` if `key` does not exist or `-1` if `key` exists but has no associated expire. + * + * @example + * ```typescript + * const result1 = client.pexpiretime("myKey"); + * console.log(result1); // Output: -2 - myKey doesn't exist. + * + * const result2 = client.set(myKey, "value"); + * const result3 = client.pexpireTime(myKey); + * console.log(result2); // Output: -1 - myKey has no associated expiration. + * + * client.expire(myKey, 60); + * const result3 = client.pexpireTime(myKey); + * console.log(result3); // Output: 123456789 - the Unix timestamp (in milliseconds) when "myKey" will expire. + * ``` + */ + public async pexpiretime(key: GlideString): Promise { + return this.createWritePromise(createPExpireTime(key)); + } + + /** + * Returns the remaining time to live of `key` that has a timeout. + * + * @see {@link https://valkey.io/commands/ttl/|valkey.io} for details. * * @param key - The key to return its timeout. - * @returns TTL in seconds, -2 if `key` does not exist or -1 if `key` exists but has no associated expire. + * @returns TTL in seconds, `-2` if `key` does not exist or `-1` if `key` exists but has no associated expire. * * @example * ```typescript @@ -1724,15 +3395,16 @@ export class BaseClient { * console.log(result); // Output: -2 - Indicates that the key doesn't exist. * ``` */ - public ttl(key: string): Promise { + public async ttl(key: GlideString): Promise { return this.createWritePromise(createTTL(key)); } /** Invokes a Lua script with its keys and arguments. - * This method simplifies the process of invoking scripts on a Redis server by using an object that represents a Lua script. + * This method simplifies the process of invoking scripts on a Valkey server by using an object that represents a Lua script. * The script loading, argument preparation, and execution will all be handled internally. If the script has not already been loaded, - * it will be loaded automatically using the Redis `SCRIPT LOAD` command. After that, it will be invoked using the Redis `EVALSHA` command - * See https://valkey.io/commands/script-load/ and https://valkey.io/commands/evalsha/ for details. + * it will be loaded automatically using the `SCRIPT LOAD` command. After that, it will be invoked using the `EVALSHA` command. + * + * @see {@link https://valkey.io/commands/script-load/|SCRIPT LOAD} and {@link https://valkey.io/commands/evalsha/|EVALSHA} on valkey.io for details. * * @param script - The Lua script to execute. * @param options - The script option that contains keys and arguments for the script. @@ -1749,7 +3421,7 @@ export class BaseClient { * console.log(result); // Output: ['foo', 'bar'] * ``` */ - public invokeScript( + public async invokeScript( script: Script, option?: ScriptOptions, ): Promise { @@ -1757,76 +3429,155 @@ export class BaseClient { hash: script.getHash(), keys: option?.keys?.map((item) => { if (typeof item === "string") { - // Convert the string to a Uint8Array + // Convert the string to a Buffer return Buffer.from(item); } else { - // If it's already a Uint8Array, just return it + // If it's already a Buffer, just return it return item; } }), args: option?.args?.map((item) => { if (typeof item === "string") { - // Convert the string to a Uint8Array + // Convert the string to a Buffer return Buffer.from(item); } else { - // If it's already a Uint8Array, just return it + // If it's already a Buffer, just return it return item; } }), }); - return this.createWritePromise(scriptInvocation); + return this.createWritePromise(scriptInvocation, { + decoder: option?.decoder, + }); + } + + /** + * Returns stream entries matching a given range of entry IDs. + * + * @see {@link https://valkey.io/commands/xrange/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param start - The starting stream entry ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.NegativeInfinity` to start with the minimum available ID. + * @param end - The ending stream entry ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.PositiveInfinity` to end with the maximum available ID. + * @param count - An optional argument specifying the maximum count of stream entries to return. + * If `count` is not provided, all stream entries in the range will be returned. + * @returns A map of stream entry ids, to an array of entries, or `null` if `count` is non-positive. + * + * @example + * ```typescript + * await client.xadd("mystream", [["field1", "value1"]], {id: "0-1"}); + * await client.xadd("mystream", [["field2", "value2"], ["field2", "value3"]], {id: "0-2"}); + * console.log(await client.xrange("mystream", InfBoundary.NegativeInfinity, InfBoundary.PositiveInfinity)); + * // Output: + * // { + * // "0-1": [["field1", "value1"]], + * // "0-2": [["field2", "value2"], ["field2", "value3"]], + * // } // Indicates the stream entry IDs and their associated field-value pairs for all stream entries in "mystream". + * ``` + */ + public async xrange( + key: string, + start: Boundary, + end: Boundary, + count?: number, + ): Promise | null> { + return this.createWritePromise(createXRange(key, start, end, count)); + } + + /** + * Returns stream entries matching a given range of entry IDs in reverse order. Equivalent to {@link xrange} but returns the + * entries in reverse order. + * + * @see {@link https://valkey.io/commands/xrevrange/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param end - The ending stream entry ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.PositiveInfinity` to end with the maximum available ID. + * @param start - The ending stream ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.NegativeInfinity` to start with the minimum available ID. + * @param count - An optional argument specifying the maximum count of stream entries to return. + * If `count` is not provided, all stream entries in the range will be returned. + * @returns A map of stream entry ids, to an array of entries, or `null` if `count` is non-positive. + * + * @example + * ```typescript + * await client.xadd("mystream", [["field1", "value1"]], {id: "0-1"}); + * await client.xadd("mystream", [["field2", "value2"], ["field2", "value3"]], {id: "0-2"}); + * console.log(await client.xrevrange("mystream", InfBoundary.PositiveInfinity, InfBoundary.NegativeInfinity)); + * // Output: + * // { + * // "0-2": [["field2", "value2"], ["field2", "value3"]], + * // "0-1": [["field1", "value1"]], + * // } // Indicates the stream entry IDs and their associated field-value pairs for all stream entries in "mystream". + * ``` + */ + public async xrevrange( + key: string, + end: Boundary, + start: Boundary, + count?: number, + ): Promise | null> { + return this.createWritePromise(createXRevRange(key, end, start, count)); } - /** Adds members with their scores to the sorted set stored at `key`. + /** + * Adds members with their scores to the sorted set stored at `key`. * If a member is already a part of the sorted set, its score is updated. - * See https://valkey.io/commands/zadd/ for more details. + * + * @see {@link https://valkey.io/commands/zadd/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param membersScoresMap - A mapping of members to their corresponding scores. - * @param options - The ZAdd options. - * @param changed - Modify the return value from the number of new elements added, to the total number of elements changed. + * @param options - (Optional) The ZAdd options - see {@link ZAddOptions}. * @returns The number of elements added to the sorted set. - * If `changed` is set, returns the number of elements updated in the sorted set. + * If {@link ZAddOptions.changed|changed} is set, returns the number of elements updated in the sorted set. * * @example * ```typescript * // Example usage of the zadd method to add elements to a sorted set - * const result = await client.zadd("my_sorted_set", \{ "member1": 10.5, "member2": 8.2 \}); + * const result = await client.zadd("my_sorted_set", { "member1": 10.5, "member2": 8.2 }); * console.log(result); // Output: 2 - Indicates that two elements have been added to the sorted set "my_sorted_set." * ``` * * @example * ```typescript * // Example usage of the zadd method to update scores in an existing sorted set - * const result = await client.zadd("existing_sorted_set", { member1: 15.0, member2: 5.5 }, options={ conditionalChange: "onlyIfExists" } , changed=true); + * const options = { conditionalChange: ConditionalChange.ONLY_IF_EXISTS, changed: true }; + * const result = await client.zadd("existing_sorted_set", { member1: 15.0, member2: 5.5 }, options); * console.log(result); // Output: 2 - Updates the scores of two existing members in the sorted set "existing_sorted_set." * ``` */ - public zadd( + public async zadd( key: string, membersScoresMap: Record, options?: ZAddOptions, - changed?: boolean, ): Promise { return this.createWritePromise( - createZAdd( - key, - membersScoresMap, - options, - changed ? "CH" : undefined, - ), + createZAdd(key, membersScoresMap, options), ); } - /** Increments the score of member in the sorted set stored at `key` by `increment`. + /** + * Increments the score of member in the sorted set stored at `key` by `increment`. * If `member` does not exist in the sorted set, it is added with `increment` as its score (as if its previous score was 0.0). * If `key` does not exist, a new sorted set with the specified member as its sole member is created. - * See https://valkey.io/commands/zadd/ for more details. + * + * @see {@link https://valkey.io/commands/zadd/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param member - A member in the sorted set to increment. * @param increment - The score to increment the member. - * @param options - The ZAdd options. + * @param options - (Optional) The ZAdd options - see {@link ZAddOptions}. * @returns The score of the member. * If there was a conflict with the options, the operation aborts and null is returned. * @@ -1844,20 +3595,21 @@ export class BaseClient { * console.log(result); // Output: null - Indicates that the member in the sorted set haven't been updated. * ``` */ - public zaddIncr( + public async zaddIncr( key: string, member: string, increment: number, options?: ZAddOptions, ): Promise { return this.createWritePromise( - createZAdd(key, { [member]: increment }, options, "INCR"), + createZAdd(key, { [member]: increment }, options, true), ); } /** Removes the specified members from the sorted set stored at `key`. * Specified members that are not a member of this set are ignored. - * See https://valkey.io/commands/zrem/ for more details. + * + * @see {@link https://valkey.io/commands/zrem/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param members - A list of members to remove from the sorted set. @@ -1878,16 +3630,18 @@ export class BaseClient { * console.log(result); // Output: 0 - Indicates that no members were removed as the sorted set "non_existing_sorted_set" does not exist. * ``` */ - public zrem(key: string, members: string[]): Promise { + public async zrem(key: string, members: string[]): Promise { return this.createWritePromise(createZRem(key, members)); } - /** Returns the cardinality (number of elements) of the sorted set stored at `key`. - * See https://valkey.io/commands/zcard/ for more details. + /** + * Returns the cardinality (number of elements) of the sorted set stored at `key`. + * + * @see {@link https://valkey.io/commands/zcard/|valkey.io} for more details. * * @param key - The key of the sorted set. * @returns The number of elements in the sorted set. - * If `key` does not exist, it is treated as an empty sorted set, and this command returns 0. + * If `key` does not exist, it is treated as an empty sorted set, and this command returns `0`. * * @example * ```typescript @@ -1903,35 +3657,128 @@ export class BaseClient { * console.log(result); // Output: 0 * ``` */ - public zcard(key: string): Promise { + public async zcard(key: GlideString): Promise { return this.createWritePromise(createZCard(key)); } /** * Returns the cardinality of the intersection of the sorted sets specified by `keys`. * - * See https://valkey.io/commands/zintercard/ for more details. - * + * @see {@link https://valkey.io/commands/zintercard/|valkey.io} for more details. * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks Since Valkey version 7.0.0. + * * @param keys - The keys of the sorted sets to intersect. * @param limit - An optional argument that can be used to specify a maximum number for the * intersection cardinality. If limit is not supplied, or if it is set to `0`, there will be no limit. * @returns The cardinality of the intersection of the given sorted sets. * - * since - Redis version 7.0.0. - * * @example * ```typescript * const cardinality = await client.zintercard(["key1", "key2"], 10); * console.log(cardinality); // Output: 3 - The intersection of the sorted sets at "key1" and "key2" has a cardinality of 3. * ``` */ - public zintercard(keys: string[], limit?: number): Promise { + public async zintercard( + keys: GlideString[], + limit?: number, + ): Promise { return this.createWritePromise(createZInterCard(keys, limit)); } + /** + * Returns the difference between the first sorted set and all the successive sorted sets. + * To get the elements with their scores, see {@link zdiffWithScores}. + * + * @see {@link https://valkey.io/commands/zdiff/|valkey.io} for more details. + * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks Since Valkey version 6.2.0. + * + * @param keys - The keys of the sorted sets. + * @param options - (Optional) See {@link DecoderOption}. + * @returns An `array` of elements representing the difference between the sorted sets. + * If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`. + * + * @example + * ```typescript + * await client.zadd("zset1", {"member1": 1.0, "member2": 2.0, "member3": 3.0}); + * await client.zadd("zset2", {"member2": 2.0}); + * await client.zadd("zset3", {"member3": 3.0}); + * const result = await client.zdiff(["zset1", "zset2", "zset3"]); + * console.log(result); // Output: ["member1"] - "member1" is in "zset1" but not "zset2" or "zset3". + * ``` + */ + public async zdiff( + keys: GlideString[], + options?: DecoderOption, + ): Promise { + return this.createWritePromise(createZDiff(keys), options); + } + + /** + * Returns the difference between the first sorted set and all the successive sorted sets, with the associated + * scores. + * + * @see {@link https://valkey.io/commands/zdiff/|valkey.io} for more details. + * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks Since Valkey version 6.2.0. + * + * @param keys - The keys of the sorted sets. + * @param options - (Optional) See {@link DecoderOption}. + * @returns A map of elements and their scores representing the difference between the sorted sets. + * If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`. + * + * @example + * ```typescript + * await client.zadd("zset1", {"member1": 1.0, "member2": 2.0, "member3": 3.0}); + * await client.zadd("zset2", {"member2": 2.0}); + * await client.zadd("zset3", {"member3": 3.0}); + * const result = await client.zdiffWithScores(["zset1", "zset2", "zset3"]); + * console.log(result); // Output: {"member1": 1.0} - "member1" is in "zset1" but not "zset2" or "zset3". + * ``` + */ + public async zdiffWithScores( + keys: GlideString[], + options?: DecoderOption, + ): Promise> { + // TODO GlideString in Record and add a test + return this.createWritePromise(createZDiffWithScores(keys), options); + } + + /** + * Calculates the difference between the first sorted set and all the successive sorted sets in `keys` and stores + * the difference as a sorted set to `destination`, overwriting it if it already exists. Non-existent keys are + * treated as empty sets. + * + * @see {@link https://valkey.io/commands/zdiffstore/|valkey.io} for more details. + * @remarks When in cluster mode, all keys in `keys` and `destination` must map to the same hash slot. + * @remarks Since Valkey version 6.2.0. + * + * @param destination - The key for the resulting sorted set. + * @param keys - The keys of the sorted sets to compare. + * @returns The number of members in the resulting sorted set stored at `destination`. + * + * @example + * ```typescript + * await client.zadd("zset1", {"member1": 1.0, "member2": 2.0}); + * await client.zadd("zset2", {"member1": 1.0}); + * const result1 = await client.zdiffstore("zset3", ["zset1", "zset2"]); + * console.log(result1); // Output: 1 - One member exists in "key1" but not "key2", and this member was stored in "zset3". + * + * const result2 = await client.zrange("zset3", {start: 0, stop: -1}); + * console.log(result2); // Output: ["member2"] - "member2" is now stored in "my_sorted_set". + * ``` + */ + public async zdiffstore( + destination: GlideString, + keys: GlideString[], + ): Promise { + return this.createWritePromise(createZDiffStore(destination, keys)); + } + /** Returns the score of `member` in the sorted set stored at `key`. - * See https://valkey.io/commands/zscore/ for more details. + * + * @see {@link https://valkey.io/commands/zscore/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param member - The member whose score is to be retrieved. @@ -1960,38 +3807,100 @@ export class BaseClient { * console.log(result); // Output: null * ``` */ - public zscore(key: string, member: string): Promise { + public async zscore(key: string, member: string): Promise { return this.createWritePromise(createZScore(key, member)); } - /** Returns the number of members in the sorted set stored at `key` with scores between `minScore` and `maxScore`. - * See https://valkey.io/commands/zcount/ for more details. + /** + * Computes the union of sorted sets given by the specified `keys` and stores the result in `destination`. + * If `destination` already exists, it is overwritten. Otherwise, a new sorted set will be created. + * To get the result directly, see {@link zunionWithScores}. + * + * @see {@link https://valkey.io/commands/zunionstore/|valkey.io} for details. + * @remarks When in cluster mode, `destination` and all keys in `keys` both must map to the same hash slot. + * @param destination - The key of the destination sorted set. + * @param keys - The keys of the sorted sets with possible formats: + * string[] - for keys only. + * KeyWeight[] - for weighted keys with score multipliers. + * @param aggregationType - Specifies the aggregation strategy to apply when combining the scores of elements. See {@link AggregationType}. + * @returns The number of elements in the resulting sorted set stored at `destination`. + * + * * @example + * ```typescript + * // Example usage of zunionstore command with an existing key + * await client.zadd("key1", {"member1": 10.5, "member2": 8.2}) + * await client.zadd("key2", {"member1": 9.5}) + * await client.zunionstore("my_sorted_set", ["key1", "key2"]) // Output: 2 - Indicates that the sorted set "my_sorted_set" contains two elements. + * await client.zrangeWithScores("my_sorted_set", RangeByIndex(0, -1)) // Output: {'member1': 20, 'member2': 8.2} - "member1" is now stored in "my_sorted_set" with score of 20 and "member2" with score of 8.2. + * await client.zunionstore("my_sorted_set", ["key1", "key2"] , AggregationType.MAX ) // Output: 2 - Indicates that the sorted set "my_sorted_set" contains two elements, and each score is the maximum score between the sets. + * await client.zrangeWithScores("my_sorted_set", RangeByIndex(0, -1)) // Output: {'member1': 10.5, 'member2': 8.2} - "member1" is now stored in "my_sorted_set" with score of 10.5 and "member2" with score of 8.2. + * await client.zunionstore("my_sorted_set", ["key1, "key2], {weights: [2, 1]}) // Output: 46 + * ``` + */ + public async zunionstore( + destination: string, + keys: string[] | KeyWeight[], + aggregationType?: AggregationType, + ): Promise { + return this.createWritePromise( + createZUnionStore(destination, keys, aggregationType), + ); + } + + /** + * Returns the scores associated with the specified `members` in the sorted set stored at `key`. + * + * @see {@link https://valkey.io/commands/zmscore/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the sorted set. + * @param members - A list of members in the sorted set. + * @returns An `array` of scores corresponding to `members`. + * If a member does not exist in the sorted set, the corresponding value in the list will be `null`. + * + * @example + * ```typescript + * const result = await client.zmscore("zset1", ["member1", "non_existent_member", "member2"]); + * console.log(result); // Output: [1.0, null, 2.0] - "member1" has a score of 1.0, "non_existent_member" does not exist in the sorted set, and "member2" has a score of 2.0. + * ``` + */ + public async zmscore( + key: string, + members: string[], + ): Promise<(number | null)[]> { + return this.createWritePromise(createZMScore(key, members)); + } + + /** + * Returns the number of members in the sorted set stored at `key` with scores between `minScore` and `maxScore`. + * + * @see {@link https://valkey.io/commands/zcount/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param minScore - The minimum score to count from. Can be positive/negative infinity, or specific score and inclusivity. * @param maxScore - The maximum score to count up to. Can be positive/negative infinity, or specific score and inclusivity. * @returns The number of members in the specified score range. - * If `key` does not exist, it is treated as an empty sorted set, and the command returns 0. - * If `minScore` is greater than `maxScore`, 0 is returned. + * If `key` does not exist, it is treated as an empty sorted set, and the command returns `0`. + * If `minScore` is greater than `maxScore`, `0` is returned. * * @example * ```typescript * // Example usage of the zcount method to count members in a sorted set within a score range - * const result = await client.zcount("my_sorted_set", { bound: 5.0, isInclusive: true }, "positiveInfinity"); + * const result = await client.zcount("my_sorted_set", { value: 5.0, isInclusive: true }, InfBoundary.PositiveInfinity); * console.log(result); // Output: 2 - Indicates that there are 2 members with scores between 5.0 (inclusive) and +inf in the sorted set "my_sorted_set". * ``` * * @example * ```typescript * // Example usage of the zcount method to count members in a sorted set within a score range - * const result = await client.zcount("my_sorted_set", { bound: 5.0, isInclusive: true }, { bound: 10.0, isInclusive: false }); + * const result = await client.zcount("my_sorted_set", { value: 5.0, isInclusive: true }, { value: 10.0, isInclusive: false }); * console.log(result); // Output: 1 - Indicates that there is one member with score between 5.0 (inclusive) and 10.0 (exclusive) in the sorted set "my_sorted_set". * ``` */ - public zcount( - key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + public async zcount( + key: GlideString, + minScore: Boundary, + maxScore: Boundary, ): Promise { return this.createWritePromise(createZCount(key, minScore, maxScore)); } @@ -1999,15 +3908,16 @@ export class BaseClient { /** Returns the specified range of elements in the sorted set stored at `key`. * ZRANGE can perform different types of range queries: by index (rank), by the score, or by lexicographical order. * - * See https://valkey.io/commands/zrange/ for more details. - * To get the elements with their scores, see `zrangeWithScores`. + * To get the elements with their scores, see {@link zrangeWithScores}. + * + * @see {@link https://valkey.io/commands/zrange/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param rangeQuery - The range query object representing the type of range query to perform. - * For range queries by index (rank), use RangeByIndex. - * For range queries by lexicographical order, use RangeByLex. - * For range queries by score, use RangeByScore. - * @param reverse - If true, reverses the sorted set, with index 0 as the element with the highest score. + * - For range queries by index (rank), use {@link RangeByIndex}. + * - For range queries by lexicographical order, use {@link RangeByLex}. + * - For range queries by score, use {@link RangeByScore}. + * @param reverse - If `true`, reverses the sorted set, with index `0` as the element with the highest score. * @returns A list of elements within the specified range. * If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty array. * @@ -2021,14 +3931,14 @@ export class BaseClient { * ```typescript * // Example usage of zrange method to retrieve members within a score range in ascending order * const result = await client.zrange("my_sorted_set", { - * start: "negativeInfinity", + * start: InfBoundary.NegativeInfinity, * stop: { value: 3, isInclusive: false }, * type: "byScore", * }); * console.log(result); // Output: ['member2', 'member3'] - Returns members with scores within the range of negative infinity to 3, in ascending order. * ``` */ - public zrange( + public async zrange( key: string, rangeQuery: RangeByScore | RangeByLex | RangeByIndex, reverse: boolean = false, @@ -2038,14 +3948,15 @@ export class BaseClient { /** Returns the specified range of elements with their scores in the sorted set stored at `key`. * Similar to ZRANGE but with a WITHSCORE flag. - * See https://valkey.io/commands/zrange/ for more details. + * + * @see {@link https://valkey.io/commands/zrange/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param rangeQuery - The range query object representing the type of range query to perform. - * For range queries by index (rank), use RangeByIndex. - * For range queries by lexicographical order, use RangeByLex. - * For range queries by score, use RangeByScore. - * @param reverse - If true, reverses the sorted set, with index 0 as the element with the highest score. + * - For range queries by index (rank), use {@link RangeByIndex}. + * - For range queries by lexicographical order, use {@link RangeByLex}. + * - For range queries by score, use {@link RangeByScore}. + * @param reverse - If `true`, reverses the sorted set, with index `0` as the element with the highest score. * @returns A map of elements and their scores within the specified range. * If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty map. * @@ -2063,14 +3974,14 @@ export class BaseClient { * ```typescript * // Example usage of zrangeWithScores method to retrieve members within a score range with their scores * const result = await client.zrangeWithScores("my_sorted_set", { - * start: "negativeInfinity", + * start: InfBoundary.NegativeInfinity, * stop: { value: 3, isInclusive: false }, * type: "byScore", * }); * console.log(result); // Output: {'member4': -2.0, 'member7': 1.5} - Returns members with scores within the range of negative infinity to 3, with their scores. * ``` */ - public zrangeWithScores( + public async zrangeWithScores( key: string, rangeQuery: RangeByScore | RangeByLex | RangeByIndex, reverse: boolean = false, @@ -2081,35 +3992,88 @@ export class BaseClient { } /** - * Computes the intersection of sorted sets given by the specified `keys` and stores the result in `destination`. - * If `destination` already exists, it is overwritten. Otherwise, a new sorted set will be created. - * To get the result directly, see `zinter_withscores`. + * Stores a specified range of elements from the sorted set at `source`, into a new + * sorted set at `destination`. If `destination` doesn't exist, a new sorted + * set is created; if it exists, it's overwritten. * - * When in cluster mode, `destination` and all keys in `keys` must map to the same hash slot. + * @see {@link https://valkey.io/commands/zrangestore/|valkey.io} for more details. + * @remarks When in cluster mode, `destination` and `source` must map to the same hash slot. + * @remarks Since Valkey version 6.2.0. * - * See https://valkey.io/commands/zinterstore/ for more details. + * @param destination - The key for the destination sorted set. + * @param source - The key of the source sorted set. + * @param rangeQuery - The range query object representing the type of range query to perform. + * - For range queries by index (rank), use {@link RangeByIndex}. + * - For range queries by lexicographical order, use {@link RangeByLex}. + * - For range queries by score, use {@link RangeByScore}. + * @param reverse - If `true`, reverses the sorted set, with index `0` as the element with the highest score. + * @returns The number of elements in the resulting sorted set. + * + * @example + * ```typescript + * // Example usage of zrangeStore to retrieve and store all members of a sorted set in ascending order. + * const result = await client.zrangeStore("destination_key", "my_sorted_set", { start: 0, stop: -1 }); + * console.log(result); // Output: 7 - "destination_key" contains a sorted set with the 7 members from "my_sorted_set". + * ``` + * @example + * ```typescript + * // Example usage of zrangeStore method to retrieve members within a score range in ascending order and store in "destination_key" + * const result = await client.zrangeStore("destination_key", "my_sorted_set", { + * start: InfBoundary.NegativeInfinity, + * stop: { value: 3, isInclusive: false }, + * type: "byScore", + * }); + * console.log(result); // Output: 5 - Stores 5 members with scores within the range of negative infinity to 3, in ascending order, in "destination_key". + * ``` + */ + public async zrangeStore( + destination: string, + source: string, + rangeQuery: RangeByScore | RangeByLex | RangeByIndex, + reverse: boolean = false, + ): Promise { + return this.createWritePromise( + createZRangeStore(destination, source, rangeQuery, reverse), + ); + } + + /** + * Computes the intersection of sorted sets given by the specified `keys` and stores the result in `destination`. + * If `destination` already exists, it is overwritten. Otherwise, a new sorted set will be created. + * To get the result directly, see {@link zinterWithScores}. + * + * @see {@link https://valkey.io/commands/zinterstore/|valkey.io} for more details. + * @remarks When in cluster mode, `destination` and all keys in `keys` must map to the same hash slot. * * @param destination - The key of the destination sorted set. * @param keys - The keys of the sorted sets with possible formats: - * string[] - for keys only. - * KeyWeight[] - for weighted keys with score multipliers. - * @param aggregationType - Specifies the aggregation strategy to apply when combining the scores of elements. See `AggregationType`. + * - `GlideString[]` - for keys only. + * - `KeyWeight[]` - for weighted keys with score multipliers. + * @param aggregationType - (Optional) Specifies the aggregation strategy to apply when combining the scores of elements. See {@link AggregationType}. + * If `aggregationType` is not specified, defaults to `AggregationType.SUM`. * @returns The number of elements in the resulting sorted set stored at `destination`. * * @example * ```typescript - * // Example usage of zinterstore command with an existing key * await client.zadd("key1", {"member1": 10.5, "member2": 8.2}) * await client.zadd("key2", {"member1": 9.5}) - * await client.zinterstore("my_sorted_set", ["key1", "key2"]) // Output: 1 - Indicates that the sorted set "my_sorted_set" contains one element. - * await client.zrange_withscores("my_sorted_set", RangeByIndex(0, -1)) // Output: {'member1': 20} - "member1" is now stored in "my_sorted_set" with score of 20. - * await client.zinterstore("my_sorted_set", ["key1", "key2"] , AggregationType.MAX ) // Output: 1 - Indicates that the sorted set "my_sorted_set" contains one element, and it's score is the maximum score between the sets. - * await client.zrange_withscores("my_sorted_set", RangeByIndex(0, -1)) // Output: {'member1': 10.5} - "member1" is now stored in "my_sorted_set" with score of 10.5. + * + * // use `zinterstore` with default aggregation and weights + * console.log(await client.zinterstore("my_sorted_set", ["key1", "key2"])) + * // Output: 1 - Indicates that the sorted set "my_sorted_set" contains one element. + * console.log(await client.zrangeWithScores("my_sorted_set", {start: 0, stop: -1})) + * // Output: {'member1': 20} - "member1" is now stored in "my_sorted_set" with score of 20. + * + * // use `zinterstore` with default weights + * console.log(await client.zinterstore("my_sorted_set", ["key1", "key2"] , AggregationType.MAX)) + * // Output: 1 - Indicates that the sorted set "my_sorted_set" contains one element, and it's score is the maximum score between the sets. + * console.log(await client.zrangeWithScores("my_sorted_set", {start: 0, stop: -1})) + * // Output: {'member1': 10.5} - "member1" is now stored in "my_sorted_set" with score of 10.5. * ``` */ - public zinterstore( - destination: string, - keys: string[] | KeyWeight[], + public async zinterstore( + destination: GlideString, + keys: GlideString[] | KeyWeight[], aggregationType?: AggregationType, ): Promise { return this.createWritePromise( @@ -2117,8 +4081,231 @@ export class BaseClient { ); } + /** + * Computes the intersection of sorted sets given by the specified `keys` and returns a list of intersecting elements. + * To get the scores as well, see {@link zinterWithScores}. + * To store the result in a key as a sorted set, see {@link zinterStore}. + * + * @remarks When in cluster mode, all keys in `keys` must map to the same hash slot. + * + * @remarks Since Valkey version 6.2.0. + * + * @see {@link https://valkey.io/commands/zinter/|valkey.io} for details. + * + * @param keys - The keys of the sorted sets. + * @param options - (Optional) See {@link DecoderOption}. + * @returns The resulting array of intersecting elements. + * + * @example + * ```typescript + * await client.zadd("key1", {"member1": 10.5, "member2": 8.2}); + * await client.zadd("key2", {"member1": 9.5}); + * const result = await client.zinter(["key1", "key2"]); + * console.log(result); // Output: ['member1'] + * ``` + */ + public async zinter( + keys: GlideString[], + options?: DecoderOption, + ): Promise { + return this.createWritePromise(createZInter(keys), options); + } + + /** + * Computes the intersection of sorted sets given by the specified `keys` and returns a list of intersecting elements with scores. + * To get the elements only, see {@link zinter}. + * To store the result in a key as a sorted set, see {@link zinterStore}. + * + * @remarks When in cluster mode, all keys in `keys` must map to the same hash slot. + * + * @see {@link https://valkey.io/commands/zinter/|valkey.io} for details. + * + * @remarks Since Valkey version 6.2.0. + * + * @param keys - The keys of the sorted sets with possible formats: + * - `GlideString[]` - for keys only. + * - `KeyWeight[]` - for weighted keys with score multipliers. + * @param options - (Optional) Additional parameters: + * - (Optional) `aggregationType`: the aggregation strategy to apply when combining the scores of elements. + * If `aggregationType` is not specified, defaults to `AggregationType.SUM`. See {@link AggregationType}. + * - (Optional) `decoder`: see {@link DecoderOption}. + * @returns The resulting sorted set with scores. + * + * @example + * ```typescript + * await client.zadd("key1", {"member1": 10.5, "member2": 8.2}); + * await client.zadd("key2", {"member1": 9.5}); + * const result1 = await client.zinterWithScores(["key1", "key2"]); + * console.log(result1); // Output: {'member1': 20} - "member1" with score of 20 is the result + * const result2 = await client.zinterWithScores(["key1", "key2"], AggregationType.MAX) + * console.log(result2); // Output: {'member1': 10.5} - "member1" with score of 10.5 is the result. + * ``` + */ + public async zinterWithScores( + keys: GlideString[] | KeyWeight[], + options?: { aggregationType?: AggregationType } & DecoderOption, + ): Promise> { + // TODO Record with GlideString and add tests + return this.createWritePromise( + createZInter(keys, options?.aggregationType, true), + options, + ); + } + + /** + * Computes the union of sorted sets given by the specified `keys` and returns a list of union elements. + * To get the scores as well, see {@link zunionWithScores}. + * + * To store the result in a key as a sorted set, see {@link zunionStore}. + * + * @remarks When in cluster mode, all keys in `keys` must map to the same hash slot. + * + * @remarks Since Valkey version 6.2.0. + * + * @see {@link https://valkey.io/commands/zunion/|valkey.io} for details. + * + * @param keys - The keys of the sorted sets. + * @returns The resulting array of union elements. + * + * @example + * ```typescript + * await client.zadd("key1", {"member1": 10.5, "member2": 8.2}); + * await client.zadd("key2", {"member1": 9.5}); + * const result = await client.zunion(["key1", "key2"]); + * console.log(result); // Output: ['member1', 'member2'] + * ``` + */ + public async zunion(keys: string[]): Promise { + return this.createWritePromise(createZUnion(keys)); + } + + /** + * Computes the intersection of sorted sets given by the specified `keys` and returns a list of union elements with scores. + * To get the elements only, see {@link zunion}. + * + * @remarks When in cluster mode, all keys in `keys` must map to the same hash slot. + * + * @see {@link https://valkey.io/commands/zunion/|valkey.io} for details. + * + * @remarks Since Valkey version 6.2.0. + * + * @param keys - The keys of the sorted sets with possible formats: + * - string[] - for keys only. + * - KeyWeight[] - for weighted keys with score multipliers. + * @param aggregationType - (Optional) Specifies the aggregation strategy to apply when combining the scores of elements. See {@link AggregationType}. + * If `aggregationType` is not specified, defaults to `AggregationType.SUM`. + * @returns The resulting sorted set with scores. + * + * @example + * ```typescript + * await client.zadd("key1", {"member1": 10.5, "member2": 8.2}); + * await client.zadd("key2", {"member1": 9.5}); + * const result1 = await client.zunionWithScores(["key1", "key2"]); + * console.log(result1); // {'member1': 20, 'member2': 8.2} + * const result2 = await client.zunionWithScores(["key1", "key2"], "MAX"); + * console.log(result2); // {'member1': 10.5, 'member2': 8.2} + * ``` + */ + public async zunionWithScores( + keys: string[] | KeyWeight[], + aggregationType?: AggregationType, + ): Promise> { + return this.createWritePromise( + createZUnion(keys, aggregationType, true), + ); + } + + /** + * Returns a random member from the sorted set stored at `key`. + * + * @see {@link https://valkey.io/commands/zrandmember/|valkey.io} for more details. + * + * @param keys - The key of the sorted set. + * @returns A string representing a random member from the sorted set. + * If the sorted set does not exist or is empty, the response will be `null`. + * + * @example + * ```typescript + * const payload1 = await client.zrandmember("mySortedSet"); + * console.log(payload1); // Output: "Glide" (a random member from the set) + * ``` + * + * @example + * ```typescript + * const payload2 = await client.zrandmember("nonExistingSortedSet"); + * console.log(payload2); // Output: null since the sorted set does not exist. + * ``` + */ + public async zrandmember(key: string): Promise { + return this.createWritePromise(createZRandMember(key)); + } + + /** + * Returns random members from the sorted set stored at `key`. + * + * @see {@link https://valkey.io/commands/zrandmember/|valkey.io} for more details. + * + * @param keys - The key of the sorted set. + * @param count - The number of members to return. + * If `count` is positive, returns unique members. + * If negative, allows for duplicates. + * @returns An `array` of members from the sorted set. + * If the sorted set does not exist or is empty, the response will be an empty `array`. + * + * @example + * ```typescript + * const payload1 = await client.zrandmemberWithCount("mySortedSet", -3); + * console.log(payload1); // Output: ["Glide", "GLIDE", "node"] + * ``` + * + * @example + * ```typescript + * const payload2 = await client.zrandmemberWithCount("nonExistingKey", 3); + * console.log(payload1); // Output: [] since the sorted set does not exist. + * ``` + */ + public async zrandmemberWithCount( + key: string, + count: number, + ): Promise { + return this.createWritePromise(createZRandMember(key, count)); + } + + /** + * Returns random members with scores from the sorted set stored at `key`. + * + * @see {@link https://valkey.io/commands/zrandmember/|valkey.io} for more details. + * + * @param keys - The key of the sorted set. + * @param count - The number of members to return. + * If `count` is positive, returns unique members. + * If negative, allows for duplicates. + * @returns A 2D `array` of `[member, score]` `arrays`, where + * member is a `string` and score is a `number`. + * If the sorted set does not exist or is empty, the response will be an empty `array`. + * + * @example + * ```typescript + * const payload1 = await client.zrandmemberWithCountWithScore("mySortedSet", -3); + * console.log(payload1); // Output: [["Glide", 1.0], ["GLIDE", 1.0], ["node", 2.0]] + * ``` + * + * @example + * ```typescript + * const payload2 = await client.zrandmemberWithCountWithScore("nonExistingKey", 3); + * console.log(payload1); // Output: [] since the sorted set does not exist. + * ``` + */ + public async zrandmemberWithCountWithScores( + key: string, + count: number, + ): Promise<[string, number][]> { + return this.createWritePromise(createZRandMember(key, count, true)); + } + /** Returns the length of the string value stored at `key`. - * See https://valkey.io/commands/strlen/ for more details. + * + * @see {@link https://valkey.io/commands/strlen/|valkey.io} for more details. * * @param key - The key to check its length. * @returns - The length of the string value stored at key @@ -2139,12 +4326,14 @@ export class BaseClient { * console.log(len2); // Output: 0 * ``` */ - public strlen(key: string): Promise { + public async strlen(key: GlideString): Promise { return this.createWritePromise(createStrlen(key)); } - /** Returns the string representation of the type of the value stored at `key`. - * See https://valkey.io/commands/type/ for more details. + /** + * Returns the string representation of the type of the value stored at `key`. + * + * @see {@link https://valkey.io/commands/type/|valkey.io} for more details. * * @param key - The `key` to check its data type. * @returns If the `key` exists, the type of the stored value is returned. Otherwise, a "none" string is returned. @@ -2165,14 +4354,17 @@ export class BaseClient { * console.log(type); // Output: 'list' * ``` */ - public type(key: string): Promise { - return this.createWritePromise(createType(key)); + public async type(key: GlideString): Promise { + return this.createWritePromise(createType(key), { + decoder: Decoder.String, + }); } /** Removes and returns the members with the lowest scores from the sorted set stored at `key`. * If `count` is provided, up to `count` members with the lowest scores are removed and returned. * Otherwise, only one member with the lowest score is removed and returned. - * See https://valkey.io/commands/zpopmin for more details. + * + * @see {@link https://valkey.io/commands/zpopmin/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param count - Specifies the quantity of members to pop. If not specified, pops one member. @@ -2194,17 +4386,48 @@ export class BaseClient { * console.log(result); // Output: {'member3': 7.5 , 'member2': 8.0} - Indicates that 'member3' with a score of 7.5 and 'member2' with a score of 8.0 have been removed from the sorted set. * ``` */ - public zpopmin( + public async zpopmin( key: string, count?: number, ): Promise> { return this.createWritePromise(createZPopMin(key, count)); } + /** + * Blocks the connection until it removes and returns a member with the lowest score from the + * first non-empty sorted set, with the given `key` being checked in the order they + * are provided. + * `BZPOPMIN` is the blocking variant of {@link zpopmin}. + * + * @see {@link https://valkey.io/commands/bzpopmin/|valkey.io} for more details. + * @remarks When in cluster mode, `keys` must map to the same hash slot. + * + * @param keys - The keys of the sorted sets. + * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of + * `0` will block indefinitely. Since 6.0.0: timeout is interpreted as a double instead of an integer. + * @param options - (Optional) See {@link DecoderOption}. + * @returns An `array` containing the key where the member was popped out, the member, itself, and the member score. + * If no member could be popped and the `timeout` expired, returns `null`. + * + * @example + * ```typescript + * const data = await client.bzpopmin(["zset1", "zset2"], 0.5); + * console.log(data); // Output: ["zset1", "a", 2]; + * ``` + */ + public async bzpopmin( + keys: GlideString[], + timeout: number, + options?: DecoderOption, + ): Promise<[GlideString, GlideString, number] | null> { + return this.createWritePromise(createBZPopMin(keys, timeout), options); + } + /** Removes and returns the members with the highest scores from the sorted set stored at `key`. * If `count` is provided, up to `count` members with the highest scores are removed and returned. * Otherwise, only one member with the highest score is removed and returned. - * See https://valkey.io/commands/zpopmax for more details. + * + * @see {@link https://valkey.io/commands/zpopmax/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param count - Specifies the quantity of members to pop. If not specified, pops one member. @@ -2226,18 +4449,50 @@ export class BaseClient { * console.log(result); // Output: {'member2': 8.0, 'member3': 7.5} - Indicates that 'member2' with a score of 8.0 and 'member3' with a score of 7.5 have been removed from the sorted set. * ``` */ - public zpopmax( + public async zpopmax( key: string, count?: number, ): Promise> { return this.createWritePromise(createZPopMax(key, count)); } - /** Returns the remaining time to live of `key` that has a timeout, in milliseconds. - * See https://valkey.io/commands/pttl for more details. + /** + * Blocks the connection until it removes and returns a member with the highest score from the + * first non-empty sorted set, with the given `key` being checked in the order they + * are provided. + * `BZPOPMAX` is the blocking variant of {@link zpopmax}. + * + * @see {@link https://valkey.io/commands/zpopmax/|valkey.io} for more details. + * @remarks When in cluster mode, `keys` must map to the same hash slot. + * + * @param keys - The keys of the sorted sets. + * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of + * `0` will block indefinitely. Since 6.0.0: timeout is interpreted as a double instead of an integer. + * @param options - (Optional) See {@link DecoderOption}. + * @returns An `array` containing the key where the member was popped out, the member, itself, and the member score. + * If no member could be popped and the `timeout` expired, returns `null`. + * + * @example + * ```typescript + * const data = await client.bzpopmax(["zset1", "zset2"], 0.5); + * console.log(data); // Output: ["zset1", "c", 2]; + * ``` + */ + public async bzpopmax( + keys: GlideString[], + timeout: number, + options?: DecoderOption, + ): Promise<[GlideString, GlideString, number] | null> { + return this.createWritePromise(createBZPopMax(keys, timeout), options); + } + + /** + * Returns the remaining time to live of `key` that has a timeout, in milliseconds. + * + * @see {@link https://valkey.io/commands/pttl/|valkey.io} for more details. * * @param key - The key to return its timeout. - * @returns TTL in milliseconds. -2 if `key` does not exist, -1 if `key` exists but has no associated expire. + * @returns TTL in milliseconds, `-2` if `key` does not exist, `-1` if `key` exists but has no associated expire. * * @example * ```typescript @@ -2260,14 +4515,15 @@ export class BaseClient { * console.log(result); // Output: -1 - Indicates that the key "key" has no associated expire. * ``` */ - public pttl(key: string): Promise { + public async pttl(key: GlideString): Promise { return this.createWritePromise(createPTTL(key)); } /** Removes all elements in the sorted set stored at `key` with rank between `start` and `end`. * Both `start` and `end` are zero-based indexes with 0 being the element with the lowest score. * These indexes can be negative numbers, where they indicate offsets starting at the element with the highest score. - * See https://valkey.io/commands/zremrangebyrank/ for more details. + * + * @see {@link https://valkey.io/commands/zremrangebyrank/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param start - The starting point of the range. @@ -2284,7 +4540,7 @@ export class BaseClient { * console.log(result); // Output: 3 - Indicates that three elements have been removed from the sorted set "my_sorted_set" between ranks 0 and 2. * ``` */ - public zremRangeByRank( + public async zremRangeByRank( key: string, start: number, end: number, @@ -2292,8 +4548,45 @@ export class BaseClient { return this.createWritePromise(createZRemRangeByRank(key, start, end)); } + /** + * Removes all elements in the sorted set stored at `key` with lexicographical order between `minLex` and `maxLex`. + * + * @see {@link https://valkey.io/commands/zremrangebylex/|valkey.io} for more details. + * + * @param key - The key of the sorted set. + * @param minLex - The minimum lex to count from. Can be positive/negative infinity, or a specific lex and inclusivity. + * @param maxLex - The maximum lex to count up to. Can be positive/negative infinity, or a specific lex and inclusivity. + * @returns The number of members removed. + * If `key` does not exist, it is treated as an empty sorted set, and the command returns 0. + * If `minLex` is greater than `maxLex`, 0 is returned. + * + * @example + * ```typescript + * // Example usage of zremRangeByLex method to remove members from a sorted set based on lexicographical order range + * const result = await client.zremRangeByLex("my_sorted_set", { value: "a", isInclusive: false }, { value: "e" }); + * console.log(result); // Output: 4 - Indicates that 4 members, with lexicographical values ranging from "a" (exclusive) to "e" (inclusive), have been removed from "my_sorted_set". + * ``` + * + * @example + * ```typescript + * // Example usage of zremRangeByLex method when the sorted set does not exist + * const result = await client.zremRangeByLex("non_existing_sorted_set", InfBoundary.NegativeInfinity, { value: "e" }); + * console.log(result); // Output: 0 - Indicates that no elements were removed. + * ``` + */ + public async zremRangeByLex( + key: string, + minLex: Boundary, + maxLex: Boundary, + ): Promise { + return this.createWritePromise( + createZRemRangeByLex(key, minLex, maxLex), + ); + } + /** Removes all elements in the sorted set stored at `key` with a score between `minScore` and `maxScore`. - * See https://valkey.io/commands/zremrangebyscore/ for more details. + * + * @see {@link https://valkey.io/commands/zremrangebyscore/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param minScore - The minimum score to remove from. Can be positive/negative infinity, or specific score and inclusivity. @@ -2305,30 +4598,63 @@ export class BaseClient { * @example * ```typescript * // Example usage of zremRangeByScore method to remove members from a sorted set based on score range - * const result = await client.zremRangeByScore("my_sorted_set", { bound: 5.0, isInclusive: true }, "positiveInfinity"); + * const result = await client.zremRangeByScore("my_sorted_set", { value: 5.0, isInclusive: true }, InfBoundary.PositiveInfinity); * console.log(result); // Output: 2 - Indicates that 2 members with scores between 5.0 (inclusive) and +inf have been removed from the sorted set "my_sorted_set". * ``` * * @example * ```typescript * // Example usage of zremRangeByScore method when the sorted set does not exist - * const result = await client.zremRangeByScore("non_existing_sorted_set", { bound: 5.0, isInclusive: true }, { bound: 10.0, isInclusive: false }); + * const result = await client.zremRangeByScore("non_existing_sorted_set", { value: 5.0, isInclusive: true }, { value: 10.0, isInclusive: false }); * console.log(result); // Output: 0 - Indicates that no members were removed as the sorted set "non_existing_sorted_set" does not exist. * ``` */ - public zremRangeByScore( + public async zremRangeByScore( key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + minScore: Boundary, + maxScore: Boundary, ): Promise { return this.createWritePromise( createZRemRangeByScore(key, minScore, maxScore), ); } + /** + * Returns the number of members in the sorted set stored at 'key' with scores between 'minLex' and 'maxLex'. + * + * @see {@link https://valkey.io/commands/zlexcount/|valkey.io} for more details. + * + * @param key - The key of the sorted set. + * @param minLex - The minimum lex to count from. Can be positive/negative infinity, or a specific lex and inclusivity. + * @param maxLex - The maximum lex to count up to. Can be positive/negative infinity, or a specific lex and inclusivity. + * @returns The number of members in the specified lex range. + * If 'key' does not exist, it is treated as an empty sorted set, and the command returns '0'. + * If maxLex is less than minLex, '0' is returned. + * + * @example + * ```typescript + * const result = await client.zlexcount("my_sorted_set", {value: "c"}, InfBoundary.PositiveInfinity); + * console.log(result); // Output: 2 - Indicates that there are 2 members with lex scores between "c" (inclusive) and positive infinity in the sorted set "my_sorted_set". + * ``` + * + * @example + * ```typescript + * const result = await client.zlexcount("my_sorted_set", {value: "c"}, {value: "k", isInclusive: false}); + * console.log(result); // Output: 1 - Indicates that there is one member with a lex score between "c" (inclusive) and "k" (exclusive) in the sorted set "my_sorted_set". + * ``` + */ + public async zlexcount( + key: string, + minLex: Boundary, + maxLex: Boundary, + ): Promise { + return this.createWritePromise(createZLexCount(key, minLex, maxLex)); + } + /** Returns the rank of `member` in the sorted set stored at `key`, with scores ordered from low to high. - * See https://valkey.io/commands/zrank for more details. - * To get the rank of `member` with its score, see `zrankWithScore`. + * To get the rank of `member` with its score, see {@link zrankWithScore}. + * + * @see {@link https://valkey.io/commands/zrank/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param member - The member whose rank is to be retrieved. @@ -2349,20 +4675,20 @@ export class BaseClient { * console.log(result); // Output: null - Indicates that "non_existing_member" is not present in the sorted set "my_sorted_set". * ``` */ - public zrank(key: string, member: string): Promise { + public async zrank(key: string, member: string): Promise { return this.createWritePromise(createZRank(key, member)); } /** Returns the rank of `member` in the sorted set stored at `key` with its score, where scores are ordered from the lowest to highest. - * See https://valkey.io/commands/zrank for more details. + * + * @see {@link https://valkey.io/commands/zrank/|valkey.io} for more details. + * @remarks Since Valkey version 7.2.0. * * @param key - The key of the sorted set. * @param member - The member whose rank is to be retrieved. * @returns A list containing the rank and score of `member` in the sorted set. * If `key` doesn't exist, or if `member` is not present in the set, null will be returned. * - * since - Redis version 7.2.0. - * * @example * ```typescript * // Example usage of zrank_withscore method to retrieve the rank and score of a member in a sorted set @@ -2377,73 +4703,199 @@ export class BaseClient { * console.log(result); // Output: null - Indicates that "non_existing_member" is not present in the sorted set "my_sorted_set". * ``` */ - public zrankWithScore( + public async zrankWithScore( key: string, member: string, ): Promise { return this.createWritePromise(createZRank(key, member, true)); } + /** + * Returns the rank of `member` in the sorted set stored at `key`, where + * scores are ordered from the highest to lowest, starting from 0. + * To get the rank of `member` with its score, see {@link zrevrankWithScore}. + * + * @see {@link https://valkey.io/commands/zrevrank/|valkey.io} for more details. + * + * @param key - The key of the sorted set. + * @param member - The member whose rank is to be retrieved. + * @returns The rank of `member` in the sorted set, where ranks are ordered from high to low based on scores. + * If `key` doesn't exist, or if `member` is not present in the set, `null` will be returned. + * + * @example + * ```typescript + * const result = await client.zrevrank("my_sorted_set", "member2"); + * console.log(result); // Output: 1 - Indicates that "member2" has the second-highest score in the sorted set "my_sorted_set". + * ``` + */ + public async zrevrank(key: string, member: string): Promise { + return this.createWritePromise(createZRevRank(key, member)); + } + + /** + * Returns the rank of `member` in the sorted set stored at `key` with its + * score, where scores are ordered from the highest to lowest, starting from 0. + * + * @see {@link https://valkey.io/commands/zrevrank/|valkey.io} for more details. + * @remarks Since Valkey version 7.2.0. + * + * @param key - The key of the sorted set. + * @param member - The member whose rank is to be retrieved. + * @returns A list containing the rank and score of `member` in the sorted set, where ranks + * are ordered from high to low based on scores. + * If `key` doesn't exist, or if `member` is not present in the set, `null` will be returned. + * + * @example + * ```typescript + * const result = await client.zrevankWithScore("my_sorted_set", "member2"); + * console.log(result); // Output: [1, 6.0] - Indicates that "member2" with score 6.0 has the second-highest score in the sorted set "my_sorted_set". + * ``` + */ + public async zrevrankWithScore( + key: string, + member: string, + ): Promise<(number[] | null)[]> { + return this.createWritePromise(createZRevRankWithScore(key, member)); + } + /** * Adds an entry to the specified stream stored at `key`. If the `key` doesn't exist, the stream is created. - * See https://valkey.io/commands/xadd/ for more details. + * + * @see {@link https://valkey.io/commands/xadd/|valkey.io} for more details. * * @param key - The key of the stream. * @param values - field-value pairs to be added to the entry. + * @param options - options detailing how to add to the stream. + * @param options - (Optional) See {@link StreamAddOptions} and {@link DecoderOption}. * @returns The id of the added entry, or `null` if `options.makeStream` is set to `false` and no stream with the matching `key` exists. */ - public xadd( - key: string, - values: [string, string][], - options?: StreamAddOptions, - ): Promise { - return this.createWritePromise(createXAdd(key, values, options)); + public async xadd( + key: GlideString, + values: [GlideString, GlideString][], + options?: StreamAddOptions & DecoderOption, + ): Promise { + return this.createWritePromise( + createXAdd(key, values, options), + options, + ); + } + + /** + * Removes the specified entries by id from a stream, and returns the number of entries deleted. + * + * @see {@link https://valkey.io/commands/xdel/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param ids - An array of entry ids. + * @returns The number of entries removed from the stream. This number may be less than the number of entries in + * `ids`, if the specified `ids` don't exist in the stream. + * + * @example + * ```typescript + * console.log(await client.xdel("key", ["1538561698944-0", "1538561698944-1"])); + * // Output is 2 since the stream marked 2 entries as deleted. + * ``` + */ + public async xdel(key: GlideString, ids: GlideString[]): Promise { + return this.createWritePromise(createXDel(key, ids)); } /** * Trims the stream stored at `key` by evicting older entries. - * See https://valkey.io/commands/xtrim/ for more details. + * + * @see {@link https://valkey.io/commands/xtrim/|valkey.io} for more details. * * @param key - the key of the stream * @param options - options detailing how to trim the stream. * @returns The number of entries deleted from the stream. If `key` doesn't exist, 0 is returned. */ - public xtrim(key: string, options: StreamTrimOptions): Promise { + public async xtrim( + key: string, + options: StreamTrimOptions, + ): Promise { return this.createWritePromise(createXTrim(key, options)); } /** * Reads entries from the given streams. - * See https://valkey.io/commands/xread/ for more details. * - * @param keys_and_ids - pairs of keys and entry ids to read from. A pair is composed of a stream's key and the id of the entry after which the stream will be read. - * @param options - options detailing how to read the stream. - * @returns A map of stream keys, to a map of stream ids, to an array of entries. + * @see {@link https://valkey.io/commands/xread/|valkey.io} for more details. + * + * @param keys_and_ids - An object of stream keys and entry IDs to read from. + * @param options - (Optional) Parameters detailing how to read the stream - see {@link StreamReadOptions}. + * @returns A `Record` of stream keys, each key is mapped to a `Record` of stream ids, to an `Array` of entries. + * * @example * ```typescript * const streamResults = await client.xread({"my_stream": "0-0", "writers": "0-0"}); - * console.log(result); // Output: { - * // "my_stream": { - * // "1526984818136-0": [["duration", "1532"], ["event-id", "5"], ["user-id", "7782813"]], - * // "1526999352406-0": [["duration", "812"], ["event-id", "9"], ["user-id", "388234"]], - * // }, "writers": { - * // "1526985676425-0": [["name", "Virginia"], ["surname", "Woolf"]], - * // "1526985685298-0": [["name", "Jane"], ["surname", "Austen"]], - * // } - * // } - * ``` - */ - public xread( + * console.log(result); // Output: + * // { + * // "my_stream": { + * // "1526984818136-0": [["duration", "1532"], ["event-id", "5"], ["user-id", "7782813"]], + * // "1526999352406-0": [["duration", "812"], ["event-id", "9"], ["user-id", "388234"]], + * // }, + * // "writers": { + * // "1526985676425-0": [["name", "Virginia"], ["surname", "Woolf"]], + * // "1526985685298-0": [["name", "Jane"], ["surname", "Austen"]], + * // } + * // } + * ``` + */ + public async xread( keys_and_ids: Record, options?: StreamReadOptions, - ): Promise>> { + ): Promise>> { return this.createWritePromise(createXRead(keys_and_ids, options)); } + /** + * Reads entries from the given streams owned by a consumer group. + * + * @see {@link https://valkey.io/commands/xreadgroup/|valkey.io} for details. + * + * @param group - The consumer group name. + * @param consumer - The group consumer. + * @param keys_and_ids - An object of stream keys and entry IDs to read from. + * Use the special entry ID of `">"` to receive only new messages. + * @param options - (Optional) Parameters detailing how to read the stream - see {@link StreamReadGroupOptions}. + * @returns A map of stream keys, each key is mapped to a map of stream ids, which is mapped to an array of entries. + * Returns `null` if there is no stream that can be served. + * + * @example + * ```typescript + * const streamResults = await client.xreadgroup("my_group", "my_consumer", {"my_stream": "0-0", "writers_stream": "0-0", "readers_stream", ">"}); + * console.log(result); // Output: + * // { + * // "my_stream": { + * // "1526984818136-0": [["duration", "1532"], ["event-id", "5"], ["user-id", "7782813"]], + * // "1526999352406-0": [["duration", "812"], ["event-id", "9"], ["user-id", "388234"]], + * // }, + * // "writers_stream": { + * // "1526985676425-0": [["name", "Virginia"], ["surname", "Woolf"]], + * // "1526985685298-0": null, // entry was deleted + * // }, + * // "readers_stream": {} // stream is empty + * // } + * ``` + */ + public async xreadgroup( + group: string, + consumer: string, + keys_and_ids: Record, + options?: StreamReadGroupOptions, + ): Promise + > | null> { + return this.createWritePromise( + createXReadGroup(group, consumer, keys_and_ids, options), + ); + } + /** * Returns the number of entries in the stream stored at `key`. * - * See https://valkey.io/commands/xlen/ for more details. + * @see {@link https://valkey.io/commands/xlen/|valkey.io} for more details. * * @param key - The key of the stream. * @returns The number of entries in the stream. If `key` does not exist, returns `0`. @@ -2454,10 +4906,517 @@ export class BaseClient { * console.log(numEntries); // Output: 2 - "my_stream" contains 2 entries. * ``` */ - public xlen(key: string): Promise { + public async xlen(key: string): Promise { return this.createWritePromise(createXLen(key)); } + /** + * Returns stream message summary information for pending messages matching a given range of IDs. + * + * @see {@link https://valkey.io/commands/xpending/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param group - The consumer group name. + * @returns An `array` that includes the summary of the pending messages. See example for more details. + * @example + * ```typescript + * console.log(await client.xpending("my_stream", "my_group")); // Output: + * // [ + * // 42, // The total number of pending messages + * // "1722643465939-0", // The smallest ID among the pending messages + * // "1722643484626-0", // The greatest ID among the pending messages + * // [ // A 2D-`array` of every consumer in the group + * // [ "consumer1", "10" ], // with at least one pending message, and the + * // [ "consumer2", "32" ], // number of pending messages it has + * // ] + * // ] + * ``` + */ + public async xpending( + key: string, + group: string, + ): Promise<[number, string, string, [string, number][]]> { + return this.createWritePromise(createXPending(key, group)); + } + + /** + * Returns an extended form of stream message information for pending messages matching a given range of IDs. + * + * @see {@link https://valkey.io/commands/xpending/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param group - The consumer group name. + * @param options - Additional options to filter entries, see {@link StreamPendingOptions}. + * @returns A 2D-`array` of 4-tuples containing extended message information. See example for more details. + * + * @example + * ```typescript + * console.log(await client.xpending("my_stream", "my_group"), { + * start: { value: "0-1", isInclusive: true }, + * end: InfBoundary.PositiveInfinity, + * count: 2, + * consumer: "consumer1" + * }); // Output: + * // [ + * // [ + * // "1722643465939-0", // The ID of the message + * // "consumer1", // The name of the consumer that fetched the message and has still to acknowledge it + * // 174431, // The number of milliseconds that elapsed since the last time this message was delivered to this consumer + * // 1 // The number of times this message was delivered + * // ], + * // [ + * // "1722643484626-0", + * // "consumer1", + * // 202231, + * // 1 + * // ] + * // ] + * ``` + */ + public async xpendingWithOptions( + key: string, + group: string, + options: StreamPendingOptions, + ): Promise<[string, string, number, number][]> { + return this.createWritePromise(createXPending(key, group, options)); + } + + /** + * Returns the list of all consumers and their attributes for the given consumer group of the + * stream stored at `key`. + * + * @see {@link https://valkey.io/commands/xinfo-consumers/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param group - The consumer group name. + * @returns An `Array` of `Records`, where each mapping contains the attributes + * of a consumer for the given consumer group of the stream at `key`. + * + * @example + * ```typescript + * const result = await client.xinfoConsumers("my_stream", "my_group"); + * console.log(result); // Output: + * // [ + * // { + * // "name": "Alice", + * // "pending": 1, + * // "idle": 9104628, + * // "inactive": 18104698 // Added in 7.2.0 + * // }, + * // ... + * // ] + * ``` + */ + public async xinfoConsumers( + key: string, + group: string, + ): Promise[]> { + return this.createWritePromise(createXInfoConsumers(key, group)); + } + + /** + * Returns the list of all consumer groups and their attributes for the stream stored at `key`. + * + * @see {@link https://valkey.io/commands/xinfo-groups/|valkey.io} for details. + * + * @param key - The key of the stream. + * @returns An array of maps, where each mapping represents the + * attributes of a consumer group for the stream at `key`. + * @example + * ```typescript + *
                              {@code
                              +     * const result = await client.xinfoGroups("my_stream");
                              +     * console.log(result); // Output:
                              +     * // [
                              +     * //     {
                              +     * //         "name": "mygroup",
                              +     * //         "consumers": 2,
                              +     * //         "pending": 2,
                              +     * //         "last-delivered-id": "1638126030001-0",
                              +     * //         "entries-read": 2,                       // Added in version 7.0.0
                              +     * //         "lag": 0                                 // Added in version 7.0.0
                              +     * //     },
                              +     * //     {
                              +     * //         "name": "some-other-group",
                              +     * //         "consumers": 1,
                              +     * //         "pending": 0,
                              +     * //         "last-delivered-id": "0-0",
                              +     * //         "entries-read": null,                    // Added in version 7.0.0
                              +     * //         "lag": 1                                 // Added in version 7.0.0
                              +     * //     }
                              +     * // ]
                              +     * ```
                              +     */
                              +    public async xinfoGroups(
                              +        key: string,
                              +    ): Promise[]> {
                              +        return this.createWritePromise(createXInfoGroups(key));
                              +    }
                              +
                              +    /**
                              +     * Changes the ownership of a pending message.
                              +     *
                              +     * @see {@link https://valkey.io/commands/xclaim/|valkey.io} for more details.
                              +     *
                              +     * @param key - The key of the stream.
                              +     * @param group - The consumer group name.
                              +     * @param consumer - The group consumer.
                              +     * @param minIdleTime - The minimum idle time for the message to be claimed.
                              +     * @param ids - An array of entry ids.
                              +     * @param options - (Optional) See {@link StreamClaimOptions} and {@link DecoderOption}.
                              +     * @returns A `Record` of message entries that are claimed by the consumer.
                              +     *
                              +     * @example
                              +     * ```typescript
                              +     * const result = await client.xclaim("myStream", "myGroup", "myConsumer", 42,
                              +     *     ["1-0", "2-0", "3-0"], { idle: 500, retryCount: 3, isForce: true });
                              +     * console.log(result); // Output:
                              +     * // {
                              +     * //     "2-0": [["duration", "1532"], ["event-id", "5"], ["user-id", "7782813"]]
                              +     * // }
                              +     * ```
                              +     */
                              +    public async xclaim(
                              +        key: GlideString,
                              +        group: GlideString,
                              +        consumer: GlideString,
                              +        minIdleTime: number,
                              +        ids: GlideString[],
                              +        options?: StreamClaimOptions & DecoderOption,
                              +    ): Promise> {
                              +        // TODO: convert Record return type to Object array
                              +        return this.createWritePromise(
                              +            createXClaim(key, group, consumer, minIdleTime, ids, options),
                              +        );
                              +    }
                              +
                              +    /**
                              +     * Transfers ownership of pending stream entries that match the specified criteria.
                              +     *
                              +     * @see {@link https://valkey.io/commands/xautoclaim/|valkey.io} for more details.
                              +     * @remarks Since Valkey version 6.2.0.
                              +     *
                              +     * @param key - The key of the stream.
                              +     * @param group - The consumer group name.
                              +     * @param consumer - The group consumer.
                              +     * @param minIdleTime - The minimum idle time for the message to be claimed.
                              +     * @param start - Filters the claimed entries to those that have an ID equal or greater than the
                              +     *     specified value.
                              +     * @param count - (Optional) Limits the number of claimed entries to the specified value.
                              +     * @returns A `tuple` containing the following elements:
                              +     *   - A stream ID to be used as the start argument for the next call to `XAUTOCLAIM`. This ID is
                              +     *     equivalent to the next ID in the stream after the entries that were scanned, or "0-0" if
                              +     *     the entire stream was scanned.
                              +     *   - A `Record` of the claimed entries.
                              +     *   - If you are using Valkey 7.0.0 or above, the response list will also include a list containing
                              +     *     the message IDs that were in the Pending Entries List but no longer exist in the stream.
                              +     *     These IDs are deleted from the Pending Entries List.
                              +     *
                              +     * @example
                              +     * ```typescript
                              +     * const result = await client.xautoclaim("myStream", "myGroup", "myConsumer", 42, "0-0", 25);
                              +     * console.log(result); // Output:
                              +     * // [
                              +     * //     "1609338788321-0",                // value to be used as `start` argument
                              +     * //                                       // for the next `xautoclaim` call
                              +     * //     {
                              +     * //         "1609338752495-0": [          // claimed entries
                              +     * //             ["field 1", "value 1"],
                              +     * //             ["field 2", "value 2"]
                              +     * //         ]
                              +     * //     },
                              +     * //     [
                              +     * //         "1594324506465-0",            // array of IDs of deleted messages,
                              +     * //         "1594568784150-0"             // included in the response only on valkey 7.0.0 and above
                              +     * //     ]
                              +     * // ]
                              +     * ```
                              +     */
                              +    public async xautoclaim(
                              +        key: GlideString,
                              +        group: GlideString,
                              +        consumer: GlideString,
                              +        minIdleTime: number,
                              +        start: GlideString,
                              +        count?: number,
                              +    ): Promise<[string, Record, string[]?]> {
                              +        // TODO: convert Record return type to Object array
                              +        return this.createWritePromise(
                              +            createXAutoClaim(key, group, consumer, minIdleTime, start, count),
                              +        );
                              +    }
                              +
                              +    /**
                              +     * Transfers ownership of pending stream entries that match the specified criteria.
                              +     *
                              +     * @see {@link https://valkey.io/commands/xautoclaim/|valkey.io} for more details.
                              +     * @remarks Since Valkey version 6.2.0.
                              +     *
                              +     * @param key - The key of the stream.
                              +     * @param group - The consumer group name.
                              +     * @param consumer - The group consumer.
                              +     * @param minIdleTime - The minimum idle time for the message to be claimed.
                              +     * @param start - Filters the claimed entries to those that have an ID equal or greater than the
                              +     *     specified value.
                              +     * @param count - (Optional) Limits the number of claimed entries to the specified value.
                              +     * @returns An `array` containing the following elements:
                              +     *   - A stream ID to be used as the start argument for the next call to `XAUTOCLAIM`. This ID is
                              +     *     equivalent to the next ID in the stream after the entries that were scanned, or "0-0" if
                              +     *     the entire stream was scanned.
                              +     *   - A list of the IDs for the claimed entries.
                              +     *   - If you are using Valkey 7.0.0 or above, the response list will also include a list containing
                              +     *     the message IDs that were in the Pending Entries List but no longer exist in the stream.
                              +     *     These IDs are deleted from the Pending Entries List.
                              +     *
                              +     * @example
                              +     * ```typescript
                              +     * const result = await client.xautoclaim("myStream", "myGroup", "myConsumer", 42, "0-0", 25);
                              +     * console.log(result); // Output:
                              +     * // [
                              +     * //     "1609338788321-0",                // value to be used as `start` argument
                              +     * //                                       // for the next `xautoclaim` call
                              +     * //     [
                              +     * //         "1609338752495-0",            // claimed entries
                              +     * //         "1609338752495-1",
                              +     * //     ],
                              +     * //     [
                              +     * //         "1594324506465-0",            // array of IDs of deleted messages,
                              +     * //         "1594568784150-0"             // included in the response only on valkey 7.0.0 and above
                              +     * //     ]
                              +     * // ]
                              +     * ```
                              +     */
                              +    public async xautoclaimJustId(
                              +        key: string,
                              +        group: string,
                              +        consumer: string,
                              +        minIdleTime: number,
                              +        start: string,
                              +        count?: number,
                              +    ): Promise<[string, string[], string[]?]> {
                              +        return this.createWritePromise(
                              +            createXAutoClaim(
                              +                key,
                              +                group,
                              +                consumer,
                              +                minIdleTime,
                              +                start,
                              +                count,
                              +                true,
                              +            ),
                              +        );
                              +    }
                              +
                              +    /**
                              +     * Changes the ownership of a pending message. This function returns an `array` with
                              +     * only the message/entry IDs, and is equivalent to using `JUSTID` in the Valkey API.
                              +     *
                              +     * @see {@link https://valkey.io/commands/xclaim/|valkey.io} for more details.
                              +     *
                              +     * @param key - The key of the stream.
                              +     * @param group - The consumer group name.
                              +     * @param consumer - The group consumer.
                              +     * @param minIdleTime - The minimum idle time for the message to be claimed.
                              +     * @param ids - An array of entry ids.
                              +     * @param options - (Optional) Stream claim options {@link StreamClaimOptions}.
                              +     * @returns An `array` of message ids claimed by the consumer.
                              +     *
                              +     * @example
                              +     * ```typescript
                              +     * const result = await client.xclaimJustId("my_stream", "my_group", "my_consumer", 42,
                              +     *     ["1-0", "2-0", "3-0"], { idle: 500, retryCount: 3, isForce: true });
                              +     * console.log(result); // Output: [ "2-0", "3-0" ]
                              +     * ```
                              +     */
                              +    public async xclaimJustId(
                              +        key: string,
                              +        group: string,
                              +        consumer: string,
                              +        minIdleTime: number,
                              +        ids: string[],
                              +        options?: StreamClaimOptions,
                              +    ): Promise {
                              +        return this.createWritePromise(
                              +            createXClaim(key, group, consumer, minIdleTime, ids, options, true),
                              +        );
                              +    }
                              +
                              +    /**
                              +     * Creates a new consumer group uniquely identified by `groupname` for the stream stored at `key`.
                              +     *
                              +     * @see {@link https://valkey.io/commands/xgroup-create/|valkey.io} for more details.
                              +     *
                              +     * @param key - The key of the stream.
                              +     * @param groupName - The newly created consumer group name.
                              +     * @param id - Stream entry ID that specifies the last delivered entry in the stream from the new
                              +     *     group’s perspective. The special ID `"$"` can be used to specify the last entry in the stream.
                              +     * @returns `"OK"`.
                              +     *
                              +     * @example
                              +     * ```typescript
                              +     * // Create the consumer group "mygroup", using zero as the starting ID:
                              +     * console.log(await client.xgroupCreate("mystream", "mygroup", "0-0")); // Output is "OK"
                              +     * ```
                              +     */
                              +    public async xgroupCreate(
                              +        key: GlideString,
                              +        groupName: GlideString,
                              +        id: GlideString,
                              +        options?: StreamGroupOptions,
                              +    ): Promise<"OK"> {
                              +        return this.createWritePromise(
                              +            createXGroupCreate(key, groupName, id, options),
                              +            { decoder: Decoder.String },
                              +        );
                              +    }
                              +
                              +    /**
                              +     * Destroys the consumer group `groupname` for the stream stored at `key`.
                              +     *
                              +     * @see {@link https://valkey.io/commands/xgroup-destroy/|valkey.io} for more details.
                              +     *
                              +     * @param key - The key of the stream.
                              +     * @param groupname - The newly created consumer group name.
                              +     * @returns `true` if the consumer group is destroyed. Otherwise, `false`.
                              +     *
                              +     * @example
                              +     * ```typescript
                              +     * // Destroys the consumer group "mygroup"
                              +     * console.log(await client.xgroupDestroy("mystream", "mygroup")); // Output is true
                              +     * ```
                              +     */
                              +    public async xgroupDestroy(
                              +        key: GlideString,
                              +        groupName: GlideString,
                              +    ): Promise {
                              +        return this.createWritePromise(createXGroupDestroy(key, groupName));
                              +    }
                              +
                              +    /**
                              +     * Returns information about the stream stored at `key`.
                              +     *
                              +     * @see {@link https://valkey.io/commands/xinfo-stream/|valkey.io} for more details.
                              +     *
                              +     * @param key - The key of the stream.
                              +     * @param fullOptions - If `true`, returns verbose information with a limit of the first 10 PEL entries.
                              +     * If `number` is specified, returns verbose information limiting the returned PEL entries.
                              +     * If `0` is specified, returns verbose information with no limit.
                              +     * @returns A {@link ReturnTypeXinfoStream} of detailed stream information for the given `key`. See
                              +     *     the example for a sample response.
                              +     *
                              +     * @example
                              +     * ```typescript
                              +     * const infoResult = await client.xinfoStream("my_stream");
                              +     * console.log(infoResult);
                              +     * // Output: {
                              +     * //   length: 2,
                              +     * //   'radix-tree-keys': 1,
                              +     * //   'radix-tree-nodes': 2,
                              +     * //   'last-generated-id': '1719877599564-1',
                              +     * //   'max-deleted-entry-id': '0-0',
                              +     * //   'entries-added': 2,
                              +     * //   'recorded-first-entry-id': '1719877599564-0',
                              +     * //   'first-entry': [ '1719877599564-0', ['some_field", "some_value', ...] ],
                              +     * //   'last-entry': [ '1719877599564-0', ['some_field", "some_value', ...] ],
                              +     * //   groups: 1,
                              +     * // }
                              +     * ```
                              +     *
                              +     * @example
                              +     * ```typescript
                              +     * const infoResult = await client.xinfoStream("my_stream", true); // default limit of 10 entries
                              +     * const infoResult = await client.xinfoStream("my_stream", 15); // limit of 15 entries
                              +     * console.log(infoResult);
                              +     * // Output: {
                              +     * //   length: 2,
                              +     * //   'radix-tree-keys': 1,
                              +     * //   'radix-tree-nodes': 2,
                              +     * //   'last-generated-id': '1719877599564-1',
                              +     * //   'max-deleted-entry-id': '0-0',
                              +     * //   'entries-added': 2,
                              +     * //   'recorded-first-entry-id': '1719877599564-0',
                              +     * //   entries: [ [ '1719877599564-0', ['some_field", "some_value', ...] ] ],
                              +     * //   groups: [ {
                              +     * //     name: 'group',
                              +     * //     'last-delivered-id': '1719877599564-0',
                              +     * //     'entries-read': 1,
                              +     * //     lag: 1,
                              +     * //     'pel-count': 1,
                              +     * //     pending: [ [ '1719877599564-0', 'consumer', 1722624726802, 1 ] ],
                              +     * //     consumers: [ {
                              +     * //         name: 'consumer',
                              +     * //         'seen-time': 1722624726802,
                              +     * //         'active-time': 1722624726802,
                              +     * //         'pel-count': 1,
                              +     * //         pending: [ [ '1719877599564-0', 'consumer', 1722624726802, 1 ] ],
                              +     * //         }
                              +     * //       ]
                              +     * //     }
                              +     * //   ]
                              +     * // }
                              +     * ```
                              +     */
                              +    public async xinfoStream(
                              +        key: string,
                              +        fullOptions?: boolean | number,
                              +    ): Promise {
                              +        return this.createWritePromise(
                              +            createXInfoStream(key, fullOptions ?? false),
                              +        );
                              +    }
                              +
                              +    /**
                              +     * Creates a consumer named `consumerName` in the consumer group `groupName` for the stream stored at `key`.
                              +     *
                              +     * @see {@link https://valkey.io/commands/xgroup-createconsumer/|valkey.io} for more details.
                              +     *
                              +     * @param key - The key of the stream.
                              +     * @param groupName - The consumer group name.
                              +     * @param consumerName - The newly created consumer.
                              +     * @returns `true` if the consumer is created. Otherwise, returns `false`.
                              +     *
                              +     * @example
                              +     * ```typescript
                              +     * // The consumer "myconsumer" was created in consumer group "mygroup" for the stream "mystream".
                              +     * console.log(await client.xgroupCreateConsumer("mystream", "mygroup", "myconsumer")); // Output is true
                              +     * ```
                              +     */
                              +    public async xgroupCreateConsumer(
                              +        key: GlideString,
                              +        groupName: GlideString,
                              +        consumerName: GlideString,
                              +    ): Promise {
                              +        return this.createWritePromise(
                              +            createXGroupCreateConsumer(key, groupName, consumerName),
                              +        );
                              +    }
                              +
                              +    /**
                              +     * Deletes a consumer named `consumerName` in the consumer group `groupName` for the stream stored at `key`.
                              +     *
                              +     * @see {@link https://valkey.io/commands/xgroup-delconsumer/|valkey.io} for more details.
                              +     *
                              +     * @param key - The key of the stream.
                              +     * @param groupName - The consumer group name.
                              +     * @param consumerName - The consumer to delete.
                              +     * @returns The number of pending messages the `consumer` had before it was deleted.
                              +     *
                              +     * * @example
                              +     * ```typescript
                              +     * // Consumer "myconsumer" was deleted, and had 5 pending messages unclaimed.
                              +     * console.log(await client.xgroupDelConsumer("mystream", "mygroup", "myconsumer")); // Output is 5
                              +     * ```
                              +     */
                              +    public async xgroupDelConsumer(
                              +        key: GlideString,
                              +        groupName: GlideString,
                              +        consumerName: GlideString,
                              +    ): Promise {
                              +        return this.createWritePromise(
                              +            createXGroupDelConsumer(key, groupName, consumerName),
                              +        );
                              +    }
                              +
                                   private readonly MAP_READ_FROM_STRATEGY: Record<
                                       ReadFrom,
                                       connection_request.ReadFrom
                              @@ -2466,14 +5425,79 @@ export class BaseClient {
                                       preferReplica: connection_request.ReadFrom.PreferReplica,
                                   };
                               
                              +    /**
                              +     * Returns the number of messages that were successfully acknowledged by the consumer group member of a stream.
                              +     * This command should be called on a pending message so that such message does not get processed again.
                              +     *
                              +     * @see {@link https://valkey.io/commands/xack/|valkey.io} for details.
                              +     *
                              +     * @param key - The key of the stream.
                              +     * @param group - The consumer group name.
                              +     * @param ids - An array of entry ids.
                              +     * @returns The number of messages that were successfully acknowledged.
                              +     *
                              +     * @example
                              +     * ```typescript
                              +     *  
                              {@code
                              +     * const entryId = await client.xadd("mystream", ["myfield", "mydata"]);
                              +     * // read messages from streamId
                              +     * const readResult = await client.xreadgroup(["myfield", "mydata"], "mygroup", "my0consumer");
                              +     * // acknowledge messages on stream
                              +     * console.log(await client.xack("mystream", "mygroup", [entryId])); // Output: 1L
                              +     * 
                              + * ``` + */ + public async xack( + key: GlideString, + group: GlideString, + ids: GlideString[], + ): Promise { + return this.createWritePromise(createXAck(key, group, ids)); + } + + /** + * Sets the last delivered ID for a consumer group. + * + * @see {@link https://valkey.io/commands/xgroup-setid|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param groupName - The consumer group name. + * @param id - The stream entry ID that should be set as the last delivered ID for the consumer + * group. + * @param entriesRead - (Optional) A value representing the number of stream entries already read by the group. + * This option can only be specified if you are using Valkey version 7.0.0 or above. + * @returns `"OK"`. + * + * * @example + * ```typescript + * console.log(await client.xgroupSetId("mystream", "mygroup", "0", 1L)); // Output is "OK" + * ``` + */ + public async xgroupSetId( + key: GlideString, + groupName: GlideString, + id: GlideString, + entriesRead?: number, + ): Promise<"OK"> { + return this.createWritePromise( + createXGroupSetid(key, groupName, id, entriesRead), + { + decoder: Decoder.String, + }, + ); + } + /** Returns the element at index `index` in the list stored at `key`. * The index is zero-based, so 0 means the first element, 1 the second element and so on. * Negative indices can be used to designate elements starting at the tail of the list. * Here, -1 means the last element, -2 means the penultimate and so forth. - * See https://valkey.io/commands/lindex/ for more details. + * + * @see {@link https://valkey.io/commands/lindex/|valkey.io} for more details. * * @param key - The `key` of the list. * @param index - The `index` of the element in the list to retrieve. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * @returns - The element at `index` in the list stored at `key`. * If `index` is out of range or if `key` does not exist, null is returned. * @@ -2491,14 +5515,20 @@ export class BaseClient { * console.log(result); // Output: 'value3' - Returns the last element in the list stored at 'my_list'. * ``` */ - public lindex(key: string, index: number): Promise { - return this.createWritePromise(createLIndex(key, index)); + public async lindex( + key: GlideString, + index: number, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createLIndex(key, index), { + decoder: decoder, + }); } /** * Inserts `element` in the list at `key` either before or after the `pivot`. * - * See https://valkey.io/commands/linsert/ for more details. + * @see {@link https://valkey.io/commands/linsert/|valkey.io} for more details. * * @param key - The key of the list. * @param position - The relative position to insert into - either `InsertPosition.Before` or @@ -2515,20 +5545,22 @@ export class BaseClient { * console.log(length); // Output: 2 - The list has a length of 2 after performing the insert. * ``` */ - public linsert( - key: string, + public async linsert( + key: GlideString, position: InsertPosition, - pivot: string, - element: string, + pivot: GlideString, + element: GlideString, ): Promise { return this.createWritePromise( createLInsert(key, position, pivot, element), ); } - /** Remove the existing timeout on `key`, turning the key from volatile (a key with an expire set) to + /** + * Removes the existing timeout on `key`, turning the key from volatile (a key with an expire set) to * persistent (a key that will never expire as no timeout is associated). - * See https://valkey.io/commands/persist/ for more details. + * + * @see {@link https://valkey.io/commands/persist/|valkey.io} for more details. * * @param key - The key to remove the existing timeout on. * @returns `false` if `key` does not exist or does not have an associated timeout, `true` if the timeout has been removed. @@ -2540,16 +5572,17 @@ export class BaseClient { * console.log(result); // Output: true - Indicates that the timeout associated with the key "my_key" was successfully removed. * ``` */ - public persist(key: string): Promise { + public async persist(key: GlideString): Promise { return this.createWritePromise(createPersist(key)); } /** * Renames `key` to `newkey`. * If `newkey` already exists it is overwritten. - * See https://valkey.io/commands/rename/ for more details. * + * @see {@link https://valkey.io/commands/rename/|valkey.io} for more details. * @remarks When in cluster mode, `key` and `newKey` must map to the same hash slot. + * * @param key - The key to rename. * @param newKey - The new name of the key. * @returns - If the `key` was successfully renamed, return "OK". If `key` does not exist, an error is thrown. @@ -2562,15 +5595,18 @@ export class BaseClient { * console.log(result); // Output: OK - Indicates successful renaming of the key "old_key" to "new_key". * ``` */ - public rename(key: string, newKey: string): Promise<"OK"> { - return this.createWritePromise(createRename(key, newKey)); + public async rename(key: GlideString, newKey: GlideString): Promise<"OK"> { + return this.createWritePromise(createRename(key, newKey), { + decoder: Decoder.String, + }); } /** * Renames `key` to `newkey` if `newkey` does not yet exist. - * See https://valkey.io/commands/renamenx/ for more details. * + * @see {@link https://valkey.io/commands/renamenx/|valkey.io} for more details. * @remarks When in cluster mode, `key` and `newKey` must map to the same hash slot. + * * @param key - The key to rename. * @param newKey - The new name of the key. * @returns - If the `key` was successfully renamed, returns `true`. Otherwise, returns `false`. @@ -2584,7 +5620,10 @@ export class BaseClient { * console.log(result); // Output: true - Indicates successful renaming of the key "old_key" to "new_key". * ``` */ - public renamenx(key: string, newKey: string): Promise { + public async renamenx( + key: GlideString, + newKey: GlideString, + ): Promise { return this.createWritePromise(createRenameNX(key, newKey)); } @@ -2592,13 +5631,15 @@ export class BaseClient { * Pop an element from the tail of the first list that is non-empty, * with the given `keys` being checked in the order that they are given. * Blocks the connection when there are no elements to pop from any of the given lists. - * See https://valkey.io/commands/brpop/ for more details. * - * @remarks - * 1. When in cluster mode, all `keys` must map to the same hash slot. - * 2. `BRPOP` is a blocking command, see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices. + * @see {@link https://valkey.io/commands/brpop/|valkey.io} for more details. + * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks `BRPOP` is a blocking command, see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices. + * * @param keys - The `keys` of the lists to pop from. * @param timeout - The `timeout` in seconds. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * @returns - An `array` containing the `key` from which the element was popped and the value of the popped element, * formatted as [key, value]. If no element could be popped and the timeout expired, returns `null`. * @@ -2609,24 +5650,29 @@ export class BaseClient { * console.log(result); // Output: ["list1", "element"] - Indicates an element "element" was popped from "list1". * ``` */ - public brpop( - keys: string[], + public async brpop( + keys: GlideString[], timeout: number, - ): Promise<[string, string] | null> { - return this.createWritePromise(createBRPop(keys, timeout)); + decoder?: Decoder, + ): Promise<[GlideString, GlideString] | null> { + return this.createWritePromise(createBRPop(keys, timeout), { + decoder: decoder, + }); } /** Blocking list pop primitive. * Pop an element from the head of the first list that is non-empty, * with the given `keys` being checked in the order that they are given. * Blocks the connection when there are no elements to pop from any of the given lists. - * See https://valkey.io/commands/blpop/ for more details. * - * @remarks - * 1. When in cluster mode, all `keys` must map to the same hash slot. - * 2. `BLPOP` is a blocking command, see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices. + * @see {@link https://valkey.io/commands/blpop/|valkey.io} for more details. + * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks `BLPOP` is a blocking command, see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices. + * * @param keys - The `keys` of the lists to pop from. * @param timeout - The `timeout` in seconds. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * @returns - An `array` containing the `key` from which the element was popped and the value of the popped element, * formatted as [key, value]. If no element could be popped and the timeout expired, returns `null`. * @@ -2636,18 +5682,21 @@ export class BaseClient { * console.log(result); // Output: ['list1', 'element'] * ``` */ - public blpop( - keys: string[], + public async blpop( + keys: GlideString[], timeout: number, - ): Promise<[string, string] | null> { - return this.createWritePromise(createBLPop(keys, timeout)); + decoder?: Decoder, + ): Promise<[GlideString, GlideString] | null> { + return this.createWritePromise(createBLPop(keys, timeout), { + decoder: decoder, + }); } /** Adds all elements to the HyperLogLog data structure stored at the specified `key`. * Creates a new structure if the `key` does not exist. * When no elements are provided, and `key` exists and is a HyperLogLog, then no operation is performed. * - * See https://valkey.io/commands/pfadd/ for more details. + * @see {@link https://valkey.io/commands/pfadd/|valkey.io} for more details. * * @param key - The key of the HyperLogLog data structure to add elements into. * @param elements - An array of members to add to the HyperLogLog stored at `key`. @@ -2661,16 +5710,19 @@ export class BaseClient { * console.log(result); // Output: 1 - Indicates that a new empty data structure was created * ``` */ - public pfadd(key: string, elements: string[]): Promise { + public async pfadd( + key: GlideString, + elements: GlideString[], + ): Promise { return this.createWritePromise(createPfAdd(key, elements)); } /** Estimates the cardinality of the data stored in a HyperLogLog structure for a single key or * calculates the combined cardinality of multiple keys by merging their HyperLogLogs temporarily. * - * See https://valkey.io/commands/pfcount/ for more details. - * + * @see {@link https://valkey.io/commands/pfcount/|valkey.io} for more details. * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * * @param keys - The keys of the HyperLogLog data structures to be analyzed. * @returns - The approximated cardinality of given HyperLogLog data structures. * The cardinality of a key that does not exist is `0`. @@ -2680,48 +5732,84 @@ export class BaseClient { * console.log(result); // Output: 4 - The approximated cardinality of the union of "hll_1" and "hll_2" * ``` */ - public pfcount(keys: string[]): Promise { + public async pfcount(keys: GlideString[]): Promise { return this.createWritePromise(createPfCount(keys)); } - /** Returns the internal encoding for the Redis object stored at `key`. + /** + * Merges multiple HyperLogLog values into a unique value. If the destination variable exists, it is + * treated as one of the source HyperLogLog data sets, otherwise a new HyperLogLog is created. + * + * @see {@link https://valkey.io/commands/pfmerge/|valkey.io} for more details. + * @remarks When in Cluster mode, all keys in `sourceKeys` and `destination` must map to the same hash slot. + * + * @param destination - The key of the destination HyperLogLog where the merged data sets will be stored. + * @param sourceKeys - The keys of the HyperLogLog structures to be merged. + * @returns A simple "OK" response. + * + * @example + * ```typescript + * await client.pfadd("hll1", ["a", "b"]); + * await client.pfadd("hll2", ["b", "c"]); + * const result = await client.pfmerge("new_hll", ["hll1", "hll2"]); + * console.log(result); // Output: OK - The value of "hll1" merged with "hll2" was stored in "new_hll". + * const count = await client.pfcount(["new_hll"]); + * console.log(count); // Output: 3 - The approximated cardinality of "new_hll" is 3. + * ``` + */ + public async pfmerge( + destination: GlideString, + sourceKeys: GlideString[], + ): Promise<"OK"> { + return this.createWritePromise(createPfMerge(destination, sourceKeys), { + decoder: Decoder.String, + }); + } + + /** + * Returns the internal encoding for the Valkey object stored at `key`. * - * See https://valkey.io/commands/object-encoding for more details. + * @see {@link https://valkey.io/commands/object-encoding/|valkey.io} for more details. * * @param key - The `key` of the object to get the internal encoding of. * @returns - If `key` exists, returns the internal encoding of the object stored at `key` as a string. - * Otherwise, returns None. + * Otherwise, returns `null`. + * * @example * ```typescript * const result = await client.objectEncoding("my_hash"); * console.log(result); // Output: "listpack" * ``` */ - public objectEncoding(key: string): Promise { - return this.createWritePromise(createObjectEncoding(key)); + public async objectEncoding(key: GlideString): Promise { + return this.createWritePromise(createObjectEncoding(key), { + decoder: Decoder.String, + }); } - /** Returns the logarithmic access frequency counter of a Redis object stored at `key`. + /** + * Returns the logarithmic access frequency counter of a Valkey object stored at `key`. * - * See https://valkey.io/commands/object-freq for more details. + * @see {@link https://valkey.io/commands/object-freq/|valkey.io} for more details. * * @param key - The `key` of the object to get the logarithmic access frequency counter of. * @returns - If `key` exists, returns the logarithmic access frequency counter of the object * stored at `key` as a `number`. Otherwise, returns `null`. + * * @example * ```typescript * const result = await client.objectFreq("my_hash"); * console.log(result); // Output: 2 - The logarithmic access frequency counter of "my_hash". * ``` */ - public objectFreq(key: string): Promise { + public async objectFreq(key: GlideString): Promise { return this.createWritePromise(createObjectFreq(key)); } /** * Returns the time in seconds since the last access to the value stored at `key`. * - * See https://valkey.io/commands/object-idletime/ for more details. + * @see {@link https://valkey.io/commands/object-idletime/|valkey.io} for more details. * * @param key - The key of the object to get the idle time of. * @returns If `key` exists, returns the idle time in seconds. Otherwise, returns `null`. @@ -2732,14 +5820,14 @@ export class BaseClient { * console.log(result); // Output: 13 - "my_hash" was last accessed 13 seconds ago. * ``` */ - public objectIdletime(key: string): Promise { + public async objectIdletime(key: GlideString): Promise { return this.createWritePromise(createObjectIdletime(key)); } /** * Returns the reference count of the object stored at `key`. * - * See https://valkey.io/commands/object-refcount/ for more details. + * @see {@link https://valkey.io/commands/object-refcount/|valkey.io} for more details. * * @param key - The `key` of the object to get the reference count of. * @returns If `key` exists, returns the reference count of the object stored at `key` as a `number`. @@ -2751,36 +5839,981 @@ export class BaseClient { * console.log(result); // Output: 2 - "my_hash" has a reference count of 2. * ``` */ - public objectRefcount(key: string): Promise { + public async objectRefcount(key: GlideString): Promise { return this.createWritePromise(createObjectRefcount(key)); } /** - * @internal + * Invokes a previously loaded function. + * + * @see {@link https://valkey.io/commands/fcall/|valkey.io} for more details. + * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks Since Valkey version 7.0.0. + * + * @param func - The function name. + * @param keys - A list of `keys` accessed by the function. To ensure the correct execution of functions, + * all names of keys that a function accesses must be explicitly provided as `keys`. + * @param args - A list of `function` arguments and it should not represent names of keys. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. + * @returns The invoked function's return value. + * + * @example + * ```typescript + * const response = await client.fcall("Deep_Thought", [], []); + * console.log(response); // Output: Returns the function's return value. + * ``` */ - protected createClientRequest( - options: BaseClientConfiguration, - ): connection_request.IConnectionRequest { - const readFrom = options.readFrom - ? this.MAP_READ_FROM_STRATEGY[options.readFrom] - : undefined; - const authenticationInfo = - options.credentials !== undefined && - "password" in options.credentials - ? { - password: options.credentials.password, - username: options.credentials.username, - } - : undefined; - const protocol = options.protocol as - | connection_request.ProtocolVersion - | undefined; - return { - protocol, - clientName: options.clientName, - addresses: options.addresses, - tlsMode: options.useTLS - ? connection_request.TlsMode.SecureTls + public async fcall( + func: GlideString, + keys: GlideString[], + args: GlideString[], + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createFCall(func, keys, args), { + decoder, + }); + } + + /** + * Invokes a previously loaded read-only function. + * + * @see {@link https://valkey.io/commands/fcall/|valkey.io} for more details. + * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks Since Valkey version 7.0.0. + * + * @param func - The function name. + * @param keys - A list of `keys` accessed by the function. To ensure the correct execution of functions, + * all names of keys that a function accesses must be explicitly provided as `keys`. + * @param args - A list of `function` arguments and it should not represent names of keys. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. + * @returns The invoked function's return value. + * + * @example + * ```typescript + * const response = await client.fcallReadOnly("Deep_Thought", ["key1"], ["Answer", "to", "the", + * "Ultimate", "Question", "of", "Life,", "the", "Universe,", "and", "Everything"]); + * console.log(response); // Output: 42 # The return value on the function that was executed. + * ``` + */ + public async fcallReadonly( + func: GlideString, + keys: GlideString[], + args: GlideString[], + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createFCallReadOnly(func, keys, args), { + decoder, + }); + } + + /** + * Returns the index of the first occurrence of `element` inside the list specified by `key`. If no + * match is found, `null` is returned. If the `count` option is specified, then the function returns + * an `array` of indices of matching elements within the list. + * + * @see {@link https://valkey.io/commands/lpos/|valkey.io} for more details. + * @remarks Since Valkey version 6.0.6. + * + * @param key - The name of the list. + * @param element - The value to search for within the list. + * @param options - (Optional) The LPOS options - see {@link LPosOptions}. + * @returns The index of `element`, or `null` if `element` is not in the list. If the `count` option + * is specified, then the function returns an `array` of indices of matching elements within the list. + * + * @example + * ```typescript + * await client.rpush("myList", ["a", "b", "c", "d", "e", "e"]); + * console.log(await client.lpos("myList", "e", { rank: 2 })); // Output: 5 - the second occurrence of "e" is at index 5. + * console.log(await client.lpos("myList", "e", { count: 3 })); // Output: [ 4, 5 ] - indices for the occurrences of "e" in list "myList". + * ``` + */ + public async lpos( + key: GlideString, + element: GlideString, + options?: LPosOptions, + ): Promise { + return this.createWritePromise(createLPos(key, element, options)); + } + + /** + * Counts the number of set bits (population counting) in the string stored at `key`. The `options` argument can + * optionally be provided to count the number of bits in a specific string interval. + * + * @see {@link https://valkey.io/commands/bitcount/|valkey.io} for more details. + * + * @param key - The key for the string to count the set bits of. + * @param options - The offset options. + * @returns If `options` is provided, returns the number of set bits in the string interval specified by `options`. + * If `options` is not provided, returns the number of set bits in the string stored at `key`. + * Otherwise, if `key` is missing, returns `0` as it is treated as an empty string. + * + * @example + * ```typescript + * console.log(await client.bitcount("my_key1")); // Output: 2 - The string stored at "my_key1" contains 2 set bits. + * console.log(await client.bitcount("my_key2", { start: 1, end: 3 })); // Output: 2 - The second to fourth bytes of the string stored at "my_key2" contain 2 set bits. + * console.log(await client.bitcount("my_key3", { start: 1, end: 1, indexType: BitmapIndexType.BIT })); // Output: 1 - Indicates that the second bit of the string stored at "my_key3" is set. + * console.log(await client.bitcount("my_key3", { start: -1, end: -1, indexType: BitmapIndexType.BIT })); // Output: 1 - Indicates that the last bit of the string stored at "my_key3" is set. + * ``` + */ + public async bitcount( + key: GlideString, + options?: BitOffsetOptions, + ): Promise { + return this.createWritePromise(createBitCount(key, options)); + } + + /** + * Adds geospatial members with their positions to the specified sorted set stored at `key`. + * If a member is already a part of the sorted set, its position is updated. + * + * @see {@link https://valkey.io/commands/geoadd/|valkey.io} for more details. + * + * @param key - The key of the sorted set. + * @param membersToGeospatialData - A mapping of member names to their corresponding positions - see + * {@link GeospatialData}. The command will report an error when the user attempts to index + * coordinates outside the specified ranges. + * @param options - The GeoAdd options - see {@link GeoAddOptions}. + * @returns The number of elements added to the sorted set. If `changed` is set to + * `true` in the options, returns the number of elements updated in the sorted set. + * + * @example + * ```typescript + * const options = {updateMode: ConditionalChange.ONLY_IF_EXISTS, changed: true}; + * const membersToCoordinates = new Map([ + * ["Palermo", { longitude: 13.361389, latitude: 38.115556 }], + * ]); + * const num = await client.geoadd("mySortedSet", membersToCoordinates, options); + * console.log(num); // Output: 1 - Indicates that the position of an existing member in the sorted set "mySortedSet" has been updated. + * ``` + */ + public async geoadd( + key: GlideString, + membersToGeospatialData: Map, + options?: GeoAddOptions, + ): Promise { + return this.createWritePromise( + createGeoAdd(key, membersToGeospatialData, options), + ); + } + + /** + * Returns the members of a sorted set populated with geospatial information using {@link geoadd}, + * which are within the borders of the area specified by a given shape. + * + * @see {@link https://valkey.io/commands/geosearch/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the sorted set. + * @param searchFrom - The query's center point options, could be one of: + * - {@link MemberOrigin} to use the position of the given existing member in the sorted set. + * - {@link CoordOrigin} to use the given longitude and latitude coordinates. + * @param searchBy - The query's shape options, could be one of: + * - {@link GeoCircleShape} to search inside circular area according to given radius. + * - {@link GeoBoxShape} to search inside an axis-aligned rectangle, determined by height and width. + * @param options - (Optional) Parameters to request additional information and configure sorting/limiting the results, + * see {@link GeoSearchResultOptions} and {@link DecoderOption}. + * @returns By default, returns an `Array` of members (locations) names. + * If any of `withCoord`, `withDist` or `withHash` are set to `true` in {@link GeoSearchResultOptions}, a 2D `Array` returned, + * where each sub-array represents a single item in the following order: + * - The member (location) name. + * - The distance from the center as a floating point `number`, in the same unit specified for `searchBy`, if `withDist` is set to `true`. + * - The geohash of the location as a integer `number`, if `withHash` is set to `true`. + * - The coordinates as a two item `array` of floating point `number`s, if `withCoord` is set to `true`. + * + * @example + * ```typescript + * const data = new Map([["Palermo", { longitude: 13.361389, latitude: 38.115556 }], ["Catania", { longitude: 15.087269, latitude: 37.502669 }]]); + * await client.geoadd("mySortedSet", data); + * // search for locations within 200 km circle around stored member named 'Palermo' + * const result1 = await client.geosearch("mySortedSet", { member: "Palermo" }, { radius: 200, unit: GeoUnit.KILOMETERS }); + * console.log(result1); // Output: ['Palermo', 'Catania'] + * + * // search for locations in 200x300 mi rectangle centered at coordinate (15, 37), requesting additional info, + * // limiting results by 2 best matches, ordered by ascending distance from the search area center + * const result2 = await client.geosearch( + * "mySortedSet", + * { position: { longitude: 15, latitude: 37 } }, + * { width: 200, height: 300, unit: GeoUnit.MILES }, + * { + * sortOrder: SortOrder.ASC, + * count: 2, + * withCoord: true, + * withDist: true, + * withHash: true, + * }, + * ); + * console.log(result2); // Output: + * // [ + * // [ + * // 'Catania', // location name + * // [ + * // 56.4413, // distance + * // 3479447370796909, // geohash of the location + * // [15.087267458438873, 37.50266842333162], // coordinates of the location + * // ], + * // ], + * // [ + * // 'Palermo', + * // [ + * // 190.4424, + * // 3479099956230698, + * // [13.361389338970184, 38.1155563954963], + * // ], + * // ], + * // ] + * ``` + */ + public async geosearch( + key: GlideString, + searchFrom: SearchOrigin, + searchBy: GeoSearchShape, + options?: GeoSearchResultOptions & DecoderOption, + ): Promise<[GlideString, [number?, number?, [number, number]?]?][]> { + return this.createWritePromise( + createGeoSearch(key, searchFrom, searchBy, options), + { decoder: options?.decoder }, + ); + } + + /** + * Searches for members in a sorted set stored at `source` representing geospatial data + * within a circular or rectangular area and stores the result in `destination`. + * + * If `destination` already exists, it is overwritten. Otherwise, a new sorted set will be created. + * + * To get the result directly, see {@link geosearch}. + * + * @see {@link https://valkey.io/commands/geosearchstore/|valkey.io} for more details. + * @remarks When in cluster mode, `destination` and `source` must map to the same hash slot. + * @remarks Since Valkey version 6.2.0. + * + * @param destination - The key of the destination sorted set. + * @param source - The key of the sorted set. + * @param searchFrom - The query's center point options, could be one of: + * - {@link MemberOrigin} to use the position of the given existing member in the sorted set. + * - {@link CoordOrigin} to use the given longitude and latitude coordinates. + * @param searchBy - The query's shape options, could be one of: + * - {@link GeoCircleShape} to search inside circular area according to given radius. + * - {@link GeoBoxShape} to search inside an axis-aligned rectangle, determined by height and width. + * @param options - (Optional) Parameters to request additional information and configure sorting/limiting the results, + * see {@link GeoSearchStoreResultOptions}. + * @returns The number of elements in the resulting sorted set stored at `destination`. + * + * @example + * ```typescript + * const data = new Map([["Palermo", { longitude: 13.361389, latitude: 38.115556 }], ["Catania", { longitude: 15.087269, latitude: 37.502669 }]]); + * await client.geoadd("mySortedSet", data); + * // search for locations within 200 km circle around stored member named 'Palermo' and store in `destination`: + * await client.geosearchstore("destination", "mySortedSet", { member: "Palermo" }, { radius: 200, unit: GeoUnit.KILOMETERS }); + * // query the stored results + * const result1 = await client.zrangeWithScores("destination", { start: 0, stop: -1 }); + * console.log(result1); // Output: + * // { + * // Palermo: 3479099956230698, // geohash of the location is stored as element's score + * // Catania: 3479447370796909 + * // } + * + * // search for locations in 200x300 mi rectangle centered at coordinate (15, 37), requesting to store distance instead of geohashes, + * // limiting results by 2 best matches, ordered by ascending distance from the search area center + * await client.geosearchstore( + * "destination", + * "mySortedSet", + * { position: { longitude: 15, latitude: 37 } }, + * { width: 200, height: 300, unit: GeoUnit.MILES }, + * { + * sortOrder: SortOrder.ASC, + * count: 2, + * storeDist: true, + * }, + * ); + * // query the stored results + * const result2 = await client.zrangeWithScores("destination", { start: 0, stop: -1 }); + * console.log(result2); // Output: + * // { + * // Palermo: 190.4424, // distance from the search area center is stored as element's score + * // Catania: 56.4413, // the distance is measured in units used for the search query (miles) + * // } + * ``` + */ + public async geosearchstore( + destination: GlideString, + source: GlideString, + searchFrom: SearchOrigin, + searchBy: GeoSearchShape, + options?: GeoSearchStoreResultOptions, + ): Promise { + return this.createWritePromise( + createGeoSearchStore( + destination, + source, + searchFrom, + searchBy, + options, + ), + ); + } + + /** + * Returns the positions (longitude, latitude) of all the specified `members` of the + * geospatial index represented by the sorted set at `key`. + * + * @see {@link https://valkey.io/commands/geopos/|valkey.io} for more details. + * + * @param key - The key of the sorted set. + * @param members - The members for which to get the positions. + * @returns A 2D `Array` which represents positions (longitude and latitude) corresponding to the + * given members. The order of the returned positions matches the order of the input members. + * If a member does not exist, its position will be `null`. + * + * @example + * ```typescript + * const data = new Map([["Palermo", { longitude: 13.361389, latitude: 38.115556 }], ["Catania", { longitude: 15.087269, latitude: 37.502669 }]]); + * await client.geoadd("mySortedSet", data); + * const result = await client.geopos("mySortedSet", ["Palermo", "Catania", "NonExisting"]); + * // When added via GEOADD, the geospatial coordinates are converted into a 52 bit geohash, so the coordinates + * // returned might not be exactly the same as the input values + * console.log(result); // Output: + * // [ + * // [13.36138933897018433, 38.11555639549629859], + * // [15.08726745843887329, 37.50266842333162032], + * // null + * // ] + * ``` + */ + public async geopos( + key: GlideString, + members: GlideString[], + ): Promise<([number, number] | null)[]> { + return this.createWritePromise(createGeoPos(key, members)); + } + + /** + * Pops a member-score pair from the first non-empty sorted set, with the given `keys` + * being checked in the order they are provided. + * + * @see {@link https://valkey.io/commands/zmpop/|valkey.io} for more details. + * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks Since Valkey version 7.0.0. + * + * @param keys - The keys of the sorted sets. + * @param modifier - The element pop criteria - either {@link ScoreFilter.MIN} or + * {@link ScoreFilter.MAX} to pop the member with the lowest/highest score accordingly. + * @param count - (Optional) The number of elements to pop. If not supplied, only one element will be popped. + * @returns A two-element `array` containing the key name of the set from which the element + * was popped, and a member-score `Record` of the popped element. + * If no member could be popped, returns `null`. + * + * @example + * ```typescript + * await client.zadd("zSet1", { one: 1.0, two: 2.0, three: 3.0 }); + * await client.zadd("zSet2", { four: 4.0 }); + * console.log(await client.zmpop(["zSet1", "zSet2"], ScoreFilter.MAX, 2)); + * // Output: [ "zSet1", { three: 3, two: 2 } ] - "three" with score 3 and "two" with score 2 were popped from "zSet1". + * ``` + */ + public async zmpop( + keys: string[], + modifier: ScoreFilter, + count?: number, + ): Promise<[string, [Record]] | null> { + return this.createWritePromise(createZMPop(keys, modifier, count)); + } + + /** + * Pops a member-score pair from the first non-empty sorted set, with the given `keys` being + * checked in the order they are provided. Blocks the connection when there are no members + * to pop from any of the given sorted sets. `BZMPOP` is the blocking variant of {@link zmpop}. + * + * @see {@link https://valkey.io/commands/bzmpop/|valkey.io} for more details. + * @remarks When in cluster mode, all `keys` must map to the same hash slot. + * @remarks `BZMPOP` is a client blocking command, see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands | Valkey Glide Wiki} for more details and best practices. + * @remarks Since Valkey version 7.0.0. + * + * @param keys - The keys of the sorted sets. + * @param modifier - The element pop criteria - either {@link ScoreFilter.MIN} or + * {@link ScoreFilter.MAX} to pop the member with the lowest/highest score accordingly. + * @param timeout - The number of seconds to wait for a blocking operation to complete. + * A value of 0 will block indefinitely. + * @param options - (Optional) Additional parameters: + * - (Optional) `count`: the number of elements to pop. If not supplied, only one element will be popped. + * - (Optional) `decoder`: see {@link DecoderOption}. + * @returns A two-element `array` containing the key name of the set from which the element + * was popped, and a member-score `Record` of the popped element. + * If no member could be popped, returns `null`. + * + * @example + * ```typescript + * await client.zadd("zSet1", { one: 1.0, two: 2.0, three: 3.0 }); + * await client.zadd("zSet2", { four: 4.0 }); + * console.log(await client.bzmpop(["zSet1", "zSet2"], ScoreFilter.MAX, 0.1, 2)); + * // Output: [ "zSet1", { three: 3, two: 2 } ] - "three" with score 3 and "two" with score 2 were popped from "zSet1". + * ``` + */ + public async bzmpop( + keys: GlideString[], + modifier: ScoreFilter, + timeout: number, + options?: { count?: number } & DecoderOption, + ): Promise<[string, Record] | null> { + // TODO GlideString in Record + return this.createWritePromise( + createBZMPop(keys, modifier, timeout, options?.count), + options, + ); + } + + /** + * Increments the score of `member` in the sorted set stored at `key` by `increment`. + * If `member` does not exist in the sorted set, it is added with `increment` as its score. + * If `key` does not exist, a new sorted set is created with the specified member as its sole member. + * + * @see {@link https://valkey.io/commands/zincrby/|valkey.io} for details. + * + * @param key - The key of the sorted set. + * @param increment - The score increment. + * @param member - A member of the sorted set. + * + * @returns The new score of `member`. + * + * @example + * ```typescript + * // Example usage of zincrBy method to increment the value of a member's score + * await client.zadd("my_sorted_set", {"member": 10.5, "member2": 8.2}); + * console.log(await client.zincrby("my_sorted_set", 1.2, "member")); + * // Output: 11.7 - The member existed in the set before score was altered, the new score is 11.7. + * console.log(await client.zincrby("my_sorted_set", -1.7, "member")); + * // Output: 10.0 - Negative increment, decrements the score. + * console.log(await client.zincrby("my_sorted_set", 5.5, "non_existing_member")); + * // Output: 5.5 - A new member is added to the sorted set with the score of 5.5. + * ``` + */ + public async zincrby( + key: GlideString, + increment: number, + member: GlideString, + ): Promise { + return this.createWritePromise(createZIncrBy(key, increment, member)); + } + + /** + * Iterates incrementally over a sorted set. + * + * @see {@link https://valkey.io/commands/zscan/|valkey.io} for more details. + * + * @param key - The key of the sorted set. + * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of + * the search. + * @param options - (Optional) The zscan options. + * @returns An `Array` of the `cursor` and the subset of the sorted set held by `key`. + * The first element is always the `cursor` for the next iteration of results. `0` will be the `cursor` + * returned on the last iteration of the sorted set. The second element is always an `Array` of the subset + * of the sorted set held in `key`. The `Array` in the second element is always a flattened series of + * `String` pairs, where the value is at even indices and the score is at odd indices. + * + * @example + * ```typescript + * // Assume "key" contains a sorted set with multiple members + * let newCursor = "0"; + * let result = []; + * + * do { + * result = await client.zscan(key1, newCursor, { + * match: "*", + * count: 5, + * }); + * newCursor = result[0]; + * console.log("Cursor: ", newCursor); + * console.log("Members: ", result[1]); + * } while (newCursor !== "0"); + * // The output of the code above is something similar to: + * // Cursor: 123 + * // Members: ['value 163', '163', 'value 114', '114', 'value 25', '25', 'value 82', '82', 'value 64', '64'] + * // Cursor: 47 + * // Members: ['value 39', '39', 'value 127', '127', 'value 43', '43', 'value 139', '139', 'value 211', '211'] + * // Cursor: 0 + * // Members: ['value 55', '55', 'value 24', '24', 'value 90', '90', 'value 113', '113'] + * ``` + */ + public async zscan( + key: string, + cursor: string, + options?: BaseScanOptions, + ): Promise<[string, string[]]> { + return this.createWritePromise(createZScan(key, cursor, options)); + } + + /** + * Returns the distance between `member1` and `member2` saved in the geospatial index stored at `key`. + * + * @see {@link https://valkey.io/commands/geodist/|valkey.io} for more details. + * + * @param key - The key of the sorted set. + * @param member1 - The name of the first member. + * @param member2 - The name of the second member. + * @param geoUnit - (Optional) The unit of distance measurement - see {@link GeoUnit}. If not specified, the {@link GeoUnit.METERS} is used as a default unit. + * @returns The distance between `member1` and `member2`. Returns `null`, if one or both members do not exist, + * or if the key does not exist. + * + * @example + * ```typescript + * const result = await client.geodist("mySortedSet", "Place1", "Place2", GeoUnit.KILOMETERS); + * console.log(num); // Output: the distance between Place1 and Place2. + * ``` + */ + public async geodist( + key: GlideString, + member1: GlideString, + member2: GlideString, + geoUnit?: GeoUnit, + ): Promise { + return this.createWritePromise( + createGeoDist(key, member1, member2, geoUnit), + ); + } + + /** + * Returns the `GeoHash` strings representing the positions of all the specified `members` in the sorted set stored at `key`. + * + * @see {@link https://valkey.io/commands/geohash/|valkey.io} for more details. + * + * @param key - The key of the sorted set. + * @param members - The array of members whose `GeoHash` strings are to be retrieved. + * @returns An array of `GeoHash` strings representing the positions of the specified members stored at `key`. + * If a member does not exist in the sorted set, a `null` value is returned for that member. + * + * @example + * ```typescript + * const result = await client.geohash("mySortedSet", ["Palermo", "Catania", "NonExisting"]); + * console.log(result); // Output: ["sqc8b49rny0", "sqdtr74hyu0", null] + * ``` + */ + public async geohash( + key: GlideString, + members: GlideString[], + ): Promise<(string | null)[]> { + return this.createWritePromise(createGeoHash(key, members), { + decoder: Decoder.String, + }); + } + + /** + * Returns all the longest common subsequences combined between strings stored at `key1` and `key2`. + * + * @see {@link https://valkey.io/commands/lcs/|valkey.io} for more details. + * @remarks When in cluster mode, `key1` and `key2` must map to the same hash slot. + * @remarks Since Valkey version 7.0.0. + * + * @param key1 - The key that stores the first string. + * @param key2 - The key that stores the second string. + * @param options - (Optional) See {@link DecoderOption}. + * @returns A `String` containing all the longest common subsequence combined between the 2 strings. + * An empty `String` is returned if the keys do not exist or have no common subsequences. + * + * @example + * ```typescript + * await client.mset({"testKey1": "abcd", "testKey2": "axcd"}); + * const result = await client.lcs("testKey1", "testKey2"); + * console.log(result); // Output: 'acd' + * ``` + */ + public async lcs( + key1: GlideString, + key2: GlideString, + options?: DecoderOption, + ): Promise { + return this.createWritePromise(createLCS(key1, key2), options); + } + + /** + * Returns the total length of all the longest common subsequences between strings stored at `key1` and `key2`. + * + * @see {@link https://valkey.io/commands/lcs/|valkey.io} for more details. + * @remarks When in cluster mode, `key1` and `key2` must map to the same hash slot. + * @remarks Since Valkey version 7.0.0. + * + * @param key1 - The key that stores the first string. + * @param key2 - The key that stores the second string. + * @param options - (Optional) See {@link DecoderOption}. + * @returns The total length of all the longest common subsequences between the 2 strings. + * + * @example + * ```typescript + * await client.mset({"testKey1": "abcd", "testKey2": "axcd"}); + * const result = await client.lcsLen("testKey1", "testKey2"); + * console.log(result); // Output: 3 + * ``` + */ + public async lcsLen( + key1: GlideString, + key2: GlideString, + options?: DecoderOption, + ): Promise { + return this.createWritePromise( + createLCS(key1, key2, { len: true }), + options, + ); + } + + /** + * Returns the indices and lengths of the longest common subsequences between strings stored at + * `key1` and `key2`. + * + * @see {@link https://valkey.io/commands/lcs/|valkey.io} for more details. + * @remarks When in cluster mode, `key1` and `key2` must map to the same hash slot. + * @remarks Since Valkey version 7.0.0. + * + * @param key1 - The key that stores the first string. + * @param key2 - The key that stores the second string. + * @param options - (Optional) Additional parameters: + * - (Optional) `withMatchLen`: if `true`, include the length of the substring matched for the each match. + * - (Optional) `minMatchLen`: the minimum length of matches to include in the result. + * @returns A `Record` containing the indices of the longest common subsequences between the + * 2 strings and the lengths of the longest common subsequences. The resulting map contains two + * keys, "matches" and "len": + * - `"len"` is mapped to the total length of the all longest common subsequences between the 2 strings + * stored as an integer. This value doesn't count towards the `minMatchLen` filter. + * - `"matches"` is mapped to a three dimensional array of integers that stores pairs + * of indices that represent the location of the common subsequences in the strings held + * by `key1` and `key2`. + * + * See example for more details. + * + * @example + * ```typescript + * await client.mset({"key1": "ohmytext", "key2": "mynewtext"}); + * const result = await client.lcsIdx("key1", "key2"); + * console.log(result); // Output: + * { + * "matches" : + * [ + * [ // first substring match is "text" + * [4, 7], // in `key1` it is located between indices 4 and 7 + * [5, 8], // and in `key2` - in between 5 and 8 + * 4 // the match length, returned if `withMatchLen` set to `true` + * ], + * [ // second substring match is "my" + * [2, 3], // in `key1` it is located between indices 2 and 3 + * [0, 1], // and in `key2` - in between 0 and 1 + * 2 // the match length, returned if `withMatchLen` set to `true` + * ] + * ], + * "len" : 6 // total length of the all matches found + * } + * ``` + */ + public async lcsIdx( + key1: GlideString, + key2: GlideString, + options?: { + withMatchLen?: boolean; + minMatchLen?: number; + }, + ): Promise> { + return this.createWritePromise( + createLCS(key1, key2, { idx: options ?? {} }), + { decoder: Decoder.String }, + ); + } + + /** + * Updates the last access time of the specified keys. + * + * @see {@link https://valkey.io/commands/touch/|valkey.io} for more details. + * @remarks When in cluster mode, the command may route to multiple nodes when `keys` map to different hash slots. + * + * @param keys - The keys to update the last access time of. + * @returns The number of keys that were updated. A key is ignored if it doesn't exist. + * + * @example + * ```typescript + * await client.set("key1", "value1"); + * await client.set("key2", "value2"); + * const result = await client.touch(["key1", "key2", "nonExistingKey"]); + * console.log(result); // Output: 2 - The last access time of 2 keys has been updated. + * ``` + */ + public async touch(keys: GlideString[]): Promise { + return this.createWritePromise(createTouch(keys)); + } + + /** + * Marks the given keys to be watched for conditional execution of a transaction. Transactions + * will only execute commands if the watched keys are not modified before execution of the + * transaction. Executing a transaction will automatically flush all previously watched keys. + * + * @see {@link https://valkey.io/commands/watch/|valkey.io} and {@link https://valkey.io/topics/transactions/#cas|Valkey Glide Wiki} for more details. + * @remarks When in cluster mode, the command may route to multiple nodes when `keys` map to different hash slots. + * + * @param keys - The keys to watch. + * @returns A simple `"OK"` response. + * + * @example + * ```typescript + * const response = await client.watch(["sampleKey"]); + * console.log(response); // Output: "OK" + * const transaction = new Transaction().set("SampleKey", "foobar"); + * const result = await client.exec(transaction); + * console.log(result); // Output: "OK" - Executes successfully and keys are unwatched. + * ``` + * ```typescript + * const response = await client.watch(["sampleKey"]); + * console.log(response); // Output: "OK" + * const transaction = new Transaction().set("SampleKey", "foobar"); + * await client.set("sampleKey", "hello world"); + * const result = await client.exec(transaction); + * console.log(result); // Output: null - null is returned when the watched key is modified before transaction execution. + * ``` + */ + public async watch(keys: GlideString[]): Promise<"OK"> { + return this.createWritePromise(createWatch(keys), { + decoder: Decoder.String, + }); + } + + /** + * Blocks the current client until all the previous write commands are successfully transferred and + * acknowledged by at least `numreplicas` of replicas. If `timeout` is reached, the command returns + * the number of replicas that were not yet reached. + * + * @see {@link https://valkey.io/commands/wait/|valkey.io} for more details. + * + * @param numreplicas - The number of replicas to reach. + * @param timeout - The timeout value specified in milliseconds. A value of 0 will block indefinitely. + * @returns The number of replicas reached by all the writes performed in the context of the current connection. + * + * @example + * ```typescript + * await client.set(key, value); + * let response = await client.wait(1, 1000); + * console.log(response); // Output: return 1 when a replica is reached or 0 if 1000ms is reached. + * ``` + */ + public async wait(numreplicas: number, timeout: number): Promise { + return this.createWritePromise(createWait(numreplicas, timeout)); + } + + /** + * Overwrites part of the string stored at `key`, starting at the specified `offset`, + * for the entire length of `value`. If the `offset` is larger than the current length of the string at `key`, + * the string is padded with zero bytes to make `offset` fit. Creates the `key` if it doesn't exist. + * + * @see {@link https://valkey.io/commands/setrange/|valkey.io} for more details. + * + * @param key - The key of the string to update. + * @param offset - The position in the string where `value` should be written. + * @param value - The string written with `offset`. + * @returns The length of the string stored at `key` after it was modified. + * + * @example + * ```typescript + * const len = await client.setrange("key", 6, "GLIDE"); + * console.log(len); // Output: 11 - New key was created with length of 11 symbols + * const value = await client.get("key"); + * console.log(result); // Output: "\0\0\0\0\0\0GLIDE" - The string was padded with zero bytes + * ``` + */ + public async setrange( + key: GlideString, + offset: number, + value: GlideString, + ): Promise { + return this.createWritePromise(createSetRange(key, offset, value)); + } + + /** + * Appends a `value` to a `key`. If `key` does not exist it is created and set as an empty string, + * so `APPEND` will be similar to {@link set} in this special case. + * + * @see {@link https://valkey.io/commands/append/|valkey.io} for more details. + * + * @param key - The key of the string. + * @param value - The key of the string. + * @returns The length of the string after appending the value. + * + * @example + * ```typescript + * const len = await client.append("key", "Hello"); + * console.log(len); + * // Output: 5 - Indicates that "Hello" has been appended to the value of "key", which was initially + * // empty, resulting in a new value of "Hello" with a length of 5 - similar to the set operation. + * len = await client.append("key", " world"); + * console.log(result); + * // Output: 11 - Indicates that " world" has been appended to the value of "key", resulting in a + * // new value of "Hello world" with a length of 11. + * ``` + */ + public async append(key: GlideString, value: GlideString): Promise { + return this.createWritePromise(createAppend(key, value)); + } + + /** + * Pops one or more elements from the first non-empty list from the provided `keys`. + * + * @see {@link https://valkey.io/commands/lmpop/|valkey.io} for more details. + * @remarks When in cluster mode, all `key`s must map to the same hash slot. + * @remarks Since Valkey version 7.0.0. + * + * @param keys - An array of keys to lists. + * @param direction - The direction based on which elements are popped from - see {@link ListDirection}. + * @param count - (Optional) The maximum number of popped elements. + * @returns A `Record` of key-name mapped array of popped elements. + * + * @example + * ```typescript + * await client.lpush("testKey", ["one", "two", "three"]); + * await client.lpush("testKey2", ["five", "six", "seven"]); + * const result = await client.lmpop(["testKey", "testKey2"], ListDirection.LEFT, 1L); + * console.log(result.get("testKey")); // Output: { "testKey": ["three"] } + * ``` + */ + public async lmpop( + keys: GlideString[], + direction: ListDirection, + count?: number, + ): Promise> { + return this.createWritePromise(createLMPop(keys, direction, count)); + } + + /** + * Blocks the connection until it pops one or more elements from the first non-empty list from the + * provided `key`. `BLMPOP` is the blocking variant of {@link lmpop}. + * + * @see {@link https://valkey.io/commands/blmpop/|valkey.io} for more details. + * @remarks When in cluster mode, all `key`s must map to the same hash slot. + * @remarks Since Valkey version 7.0.0. + * + * @param keys - An array of keys to lists. + * @param direction - The direction based on which elements are popped from - see {@link ListDirection}. + * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of `0` will block indefinitely. + * @param count - (Optional) The maximum number of popped elements. + * @returns - A `Record` of `key` name mapped array of popped elements. + * If no member could be popped and the timeout expired, returns `null`. + * + * @example + * ```typescript + * await client.lpush("testKey", ["one", "two", "three"]); + * await client.lpush("testKey2", ["five", "six", "seven"]); + * const result = await client.blmpop(["testKey", "testKey2"], ListDirection.LEFT, 0.1, 1L); + * console.log(result.get("testKey")); // Output: { "testKey": ["three"] } + * ``` + */ + public async blmpop( + keys: GlideString[], + direction: ListDirection, + timeout: number, + count?: number, + ): Promise> { + return this.createWritePromise( + createBLMPop(keys, direction, timeout, count), + ); + } + + /** + * Lists the currently active channels. + * The command is routed to all nodes, and aggregates the response to a single array. + * + * @see {@link https://valkey.io/commands/pubsub-channels/|valkey.io} for more details. + * + * @param options - (Optional) Additional parameters: + * - (Optional) `pattern`: A glob-style pattern to match active channels. + * If not provided, all active channels are returned. + * - (Optional) `decoder`: see {@link DecoderOption}. + * @returns A list of currently active channels matching the given pattern. + * If no pattern is specified, all active channels are returned. + * + * @example + * ```typescript + * const channels = await client.pubsubChannels(); + * console.log(channels); // Output: ["channel1", "channel2"] + * + * const newsChannels = await client.pubsubChannels("news.*"); + * console.log(newsChannels); // Output: ["news.sports", "news.weather"] + * ``` + */ + public async pubsubChannels( + options?: { pattern?: GlideString } & DecoderOption, + ): Promise { + return this.createWritePromise(createPubSubChannels(options?.pattern), { + decoder: options?.decoder, + }); + } + + /** + * Returns the number of unique patterns that are subscribed to by clients. + * + * Note: This is the total number of unique patterns all the clients are subscribed to, + * not the count of clients subscribed to patterns. + * The command is routed to all nodes, and aggregates the response to the sum of all pattern subscriptions. + * + * @see {@link https://valkey.io/commands/pubsub-numpat/|valkey.io} for more details. + * + * @returns The number of unique patterns. + * + * @example + * ```typescript + * const patternCount = await client.pubsubNumpat(); + * console.log(patternCount); // Output: 3 + * ``` + */ + public async pubsubNumPat(): Promise { + return this.createWritePromise(createPubSubNumPat()); + } + + /** + * Returns the number of subscribers (exclusive of clients subscribed to patterns) for the specified channels. + * + * Note that it is valid to call this command without channels. In this case, it will just return an empty map. + * The command is routed to all nodes, and aggregates the response to a single map of the channels and their number of subscriptions. + * + * @see {@link https://valkey.io/commands/pubsub-numsub/|valkey.io} for more details. + * + * @param channels - The list of channels to query for the number of subscribers. + * If not provided, returns an empty map. + * @returns A map where keys are the channel names and values are the number of subscribers. + * + * @example + * ```typescript + * const result1 = await client.pubsubNumsub(["channel1", "channel2"]); + * console.log(result1); // Output: { "channel1": 3, "channel2": 5 } + * + * const result2 = await client.pubsubNumsub(); + * console.log(result2); // Output: {} + * ``` + */ + public async pubsubNumSub( + channels?: string[], + ): Promise> { + return this.createWritePromise(createPubSubNumSub(channels)); + } + + /** + * @internal + */ + protected createClientRequest( + options: BaseClientConfiguration, + ): connection_request.IConnectionRequest { + const readFrom = options.readFrom + ? this.MAP_READ_FROM_STRATEGY[options.readFrom] + : connection_request.ReadFrom.Primary; + const authenticationInfo = + options.credentials !== undefined && + "password" in options.credentials + ? { + password: options.credentials.password, + username: options.credentials.username, + } + : undefined; + const protocol = options.protocol as + | connection_request.ProtocolVersion + | undefined; + return { + protocol, + clientName: options.clientName, + addresses: options.addresses, + tlsMode: options.useTLS + ? connection_request.TlsMode.SecureTls : connection_request.TlsMode.NoTls, requestTimeout: options.requestTimeout, clusterModeEnabled: false, @@ -2823,7 +6856,12 @@ export class BaseClient { public close(errorMessage?: string): void { this.isClosed = true; this.promiseCallbackFunctions.forEach(([, reject]) => { - reject(new ClosingError(errorMessage)); + reject(new ClosingError(errorMessage || "")); + }); + + // Handle pubsub futures + this.pubsubFutures.forEach(([, reject]) => { + reject(new ClosingError(errorMessage || "")); }); Logger.log("info", "Client lifetime", "disposing of client"); this.socket.end(); @@ -2873,10 +6911,17 @@ export class BaseClient { ): Promise { const path = await StartSocketConnection(); const socket = await this.GetSocket(path); - return await this.__createClientInternal( - options, - socket, - constructor, - ); + + try { + return await this.__createClientInternal( + options, + socket, + constructor, + ); + } catch (err) { + // Ensure socket is closed + socket.end(); + throw err; + } } } diff --git a/node/src/Commands.ts b/node/src/Commands.ts index fc83f4fe58..e19888f629 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -5,11 +5,18 @@ import { createLeakedStringVec, MAX_REQUEST_ARGS_LEN } from "glide-rs"; import Long from "long"; +/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ +import { BaseClient, HashDataType } from "src/BaseClient"; +/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ +import { GlideClient } from "src/GlideClient"; +/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ +import { GlideClusterClient } from "src/GlideClusterClient"; +import { GlideString } from "./BaseClient"; import { command_request } from "./ProtobufMessage"; import RequestType = command_request.RequestType; -function isLargeCommand(args: BulkString[]) { +function isLargeCommand(args: GlideString[]) { let lenSum = 0; for (const arg of args) { @@ -23,12 +30,10 @@ function isLargeCommand(args: BulkString[]) { return false; } -type BulkString = string | Uint8Array; - /** * Convert a string array into Uint8Array[] */ -function toBuffersArray(args: BulkString[]) { +function toBuffersArray(args: GlideString[]) { const argsBytes: Uint8Array[] = []; for (const arg of args) { @@ -62,7 +67,7 @@ export function parseInfoResponse(response: string): Record { function createCommand( requestType: command_request.RequestType, - args: BulkString[], + args: GlideString[], ): command_request.Command { const singleCommand = command_request.Command.create({ requestType, @@ -87,10 +92,32 @@ function createCommand( /** * @internal */ -export function createGet(key: string): command_request.Command { +export function createGet(key: GlideString): command_request.Command { return createCommand(RequestType.Get, [key]); } +/** + * @internal + */ +export function createGetDel(key: GlideString): command_request.Command { + return createCommand(RequestType.GetDel, [key]); +} + +/** + * @internal + */ +export function createGetRange( + key: GlideString, + start: number, + end: number, +): command_request.Command { + return createCommand(RequestType.GetRange, [ + key, + start.toString(), + end.toString(), + ]); +} + export type SetOptions = { /** * `onlyIfDoesNotExist` - Only set the key if it does not already exist. @@ -115,26 +142,7 @@ export type SetOptions = { */ | "keepExisting" | { - type: /** - * Set the specified expire time, in seconds. Equivalent to - * `EX` in the Redis API. - */ - | "seconds" - /** - * Set the specified expire time, in milliseconds. Equivalent - * to `PX` in the Redis API. - */ - | "milliseconds" - /** - * Set the specified Unix time at which the key will expire, - * in seconds. Equivalent to `EXAT` in the Redis API. - */ - | "unixSeconds" - /** - * Set the specified Unix time at which the key will expire, - * in milliseconds. Equivalent to `PXAT` in the Redis API. - */ - | "unixMilliseconds"; + type: TimeUnit; count: number; }; }; @@ -143,8 +151,8 @@ export type SetOptions = { * @internal */ export function createSet( - key: BulkString, - value: BulkString, + key: GlideString, + value: GlideString, options?: SetOptions, ): command_request.Command { const args = [key, value]; @@ -160,28 +168,23 @@ export function createSet( args.push("GET"); } - if ( - options.expiry && - options.expiry !== "keepExisting" && - !Number.isInteger(options.expiry.count) - ) { - throw new Error( - `Received expiry '${JSON.stringify( - options.expiry, - )}'. Count must be an integer`, - ); - } + if (options.expiry) { + if ( + options.expiry !== "keepExisting" && + !Number.isInteger(options.expiry.count) + ) { + throw new Error( + `Received expiry '${JSON.stringify( + options.expiry, + )}'. Count must be an integer`, + ); + } - if (options.expiry === "keepExisting") { - args.push("KEEPTTL"); - } else if (options.expiry?.type === "seconds") { - args.push("EX", options.expiry.count.toString()); - } else if (options.expiry?.type === "milliseconds") { - args.push("PX", options.expiry.count.toString()); - } else if (options.expiry?.type === "unixSeconds") { - args.push("EXAT", options.expiry.count.toString()); - } else if (options.expiry?.type === "unixMilliseconds") { - args.push("PXAT", options.expiry.count.toString()); + if (options.expiry === "keepExisting") { + args.push("KEEPTTL"); + } else { + args.push(options.expiry.type, options.expiry.count.toString()); + } } } @@ -266,8 +269,8 @@ export enum InfoOptions { /** * @internal */ -export function createPing(str?: string): command_request.Command { - const args: string[] = str == undefined ? [] : [str]; +export function createPing(str?: GlideString): command_request.Command { + const args: GlideString[] = str == undefined ? [] : [str]; return createCommand(RequestType.Ping, args); } @@ -282,7 +285,7 @@ export function createInfo(options?: InfoOptions[]): command_request.Command { /** * @internal */ -export function createDel(keys: string[]): command_request.Command { +export function createDel(keys: GlideString[]): command_request.Command { return createCommand(RequestType.Del, keys); } @@ -317,7 +320,7 @@ export function createConfigResetStat(): command_request.Command { /** * @internal */ -export function createMGet(keys: string[]): command_request.Command { +export function createMGet(keys: GlideString[]): command_request.Command { return createCommand(RequestType.MGet, keys); } @@ -333,7 +336,19 @@ export function createMSet( /** * @internal */ -export function createIncr(key: string): command_request.Command { +export function createMSetNX( + keyValueMap: Record, +): command_request.Command { + return createCommand( + RequestType.MSetNX, + Object.entries(keyValueMap).flat(), + ); +} + +/** + * @internal + */ +export function createIncr(key: GlideString): command_request.Command { return createCommand(RequestType.Incr, [key]); } @@ -341,7 +356,7 @@ export function createIncr(key: string): command_request.Command { * @internal */ export function createIncrBy( - key: string, + key: GlideString, amount: number, ): command_request.Command { return createCommand(RequestType.IncrBy, [key, amount.toString()]); @@ -351,7 +366,7 @@ export function createIncrBy( * @internal */ export function createIncrByFloat( - key: string, + key: GlideString, amount: number, ): command_request.Command { return createCommand(RequestType.IncrByFloat, [key, amount.toString()]); @@ -375,7 +390,7 @@ export function createConfigGet(parameters: string[]): command_request.Command { * @internal */ export function createConfigSet( - parameters: Record, + parameters: Record, ): command_request.Command { return createCommand( RequestType.ConfigSet, @@ -387,8 +402,8 @@ export function createConfigSet( * @internal */ export function createHGet( - key: string, - field: string, + key: GlideString, + field: GlideString, ): command_request.Command { return createCommand(RequestType.HGet, [key, field]); } @@ -397,22 +412,36 @@ export function createHGet( * @internal */ export function createHSet( - key: string, - fieldValueMap: Record, + key: GlideString, + fieldValueList: HashDataType, ): command_request.Command { return createCommand( RequestType.HSet, - [key].concat(Object.entries(fieldValueMap).flat()), + [key].concat( + fieldValueList + .map((fieldValueObject) => [ + fieldValueObject.field, + fieldValueObject.value, + ]) + .flat(), + ), ); } +/** + * @internal + */ +export function createHKeys(key: GlideString): command_request.Command { + return createCommand(RequestType.HKeys, [key]); +} + /** * @internal */ export function createHSetNX( - key: string, - field: string, - value: string, + key: GlideString, + field: GlideString, + value: GlideString, ): command_request.Command { return createCommand(RequestType.HSetNX, [key, field, value]); } @@ -420,7 +449,7 @@ export function createHSetNX( /** * @internal */ -export function createDecr(key: string): command_request.Command { +export function createDecr(key: GlideString): command_request.Command { return createCommand(RequestType.Decr, [key]); } @@ -428,18 +457,359 @@ export function createDecr(key: string): command_request.Command { * @internal */ export function createDecrBy( - key: string, + key: GlideString, amount: number, ): command_request.Command { return createCommand(RequestType.DecrBy, [key, amount.toString()]); } +/** + * Enumeration defining the bitwise operation to use in the {@link BaseClient.bitop|bitop} command. Specifies the + * bitwise operation to perform between the passed in keys. + */ +export enum BitwiseOperation { + AND = "AND", + OR = "OR", + XOR = "XOR", + NOT = "NOT", +} + +/** + * @internal + */ +export function createBitOp( + operation: BitwiseOperation, + destination: GlideString, + keys: GlideString[], +): command_request.Command { + return createCommand(RequestType.BitOp, [operation, destination, ...keys]); +} + +/** + * @internal + */ +export function createGetBit( + key: GlideString, + offset: number, +): command_request.Command { + return createCommand(RequestType.GetBit, [key, offset.toString()]); +} + +/** + * @internal + */ +export function createSetBit( + key: GlideString, + offset: number, + value: number, +): command_request.Command { + return createCommand(RequestType.SetBit, [ + key, + offset.toString(), + value.toString(), + ]); +} + +/** + * Represents a signed or unsigned argument encoding for the {@link BaseClient.bitfield|bitfield} or + * {@link BaseClient.bitfieldReadOnly|bitfieldReadOnly} commands. + */ +export interface BitEncoding { + /** + * Returns the encoding as a string argument to be used in the {@link BaseClient.bitfield|bitfield} or + * {@link BaseClient.bitfieldReadOnly|bitfieldReadOnly} commands. + * + * @returns The encoding as a string argument. + */ + toArg(): string; +} + +/** + * Represents a signed argument encoding. + */ +export class SignedEncoding implements BitEncoding { + private static readonly SIGNED_ENCODING_PREFIX = "i"; + private readonly encoding: string; + + /** + * Creates an instance of SignedEncoding. + * + * @param encodingLength - The bit size of the encoding. Must be less than 65 bits long. + */ + constructor(encodingLength: number) { + this.encoding = `${SignedEncoding.SIGNED_ENCODING_PREFIX}${encodingLength.toString()}`; + } + + public toArg(): string { + return this.encoding; + } +} + +/** + * Represents an unsigned argument encoding. + */ +export class UnsignedEncoding implements BitEncoding { + private static readonly UNSIGNED_ENCODING_PREFIX = "u"; + private readonly encoding: string; + + /** + * Creates an instance of UnsignedEncoding. + * + * @param encodingLength - The bit size of the encoding. Must be less than 64 bits long. + */ + constructor(encodingLength: number) { + this.encoding = `${UnsignedEncoding.UNSIGNED_ENCODING_PREFIX}${encodingLength.toString()}`; + } + + public toArg(): string { + return this.encoding; + } +} + +/** + * Represents an offset for an array of bits for the {@link BaseClient.bitfield|bitfield} or + * {@link BaseClient.bitfieldReadOnly|bitfieldReadOnly} commands. + */ +export interface BitFieldOffset { + /** + * Returns the offset as a string argument to be used in the {@link BaseClient.bitfield|bitfield} or + * {@link BaseClient.bitfieldReadOnly|bitfieldReadOnly} commands. + * + * @returns The offset as a string argument. + */ + toArg(): string; +} + +/** + * Represents an offset in an array of bits for the {@link BaseClient.bitfield|bitfield} or + * {@link BaseClient.bitfieldReadOnly|bitfieldReadOnly} commands. + * + * For example, if we have the binary `01101001` with offset of 1 for an unsigned encoding of size 4, then the value + * is 13 from `0(1101)001`. + */ +export class BitOffset implements BitFieldOffset { + private readonly offset: string; + + /** + * Creates an instance of BitOffset. + * + * @param offset - The bit index offset in the array of bits. Must be greater than or equal to 0. + */ + constructor(offset: number) { + this.offset = offset.toString(); + } + + public toArg(): string { + return this.offset; + } +} + +/** + * Represents an offset in an array of bits for the {@link BaseClient.bitfield|bitfield} or + * {@link BaseClient.bitfieldReadOnly|bitfieldReadOnly} commands. The bit offset index is calculated as the numerical + * value of the offset multiplied by the encoding value. + * + * For example, if we have the binary 01101001 with offset multiplier of 1 for an unsigned encoding of size 4, then the + * value is 9 from `0110(1001)`. + */ +export class BitOffsetMultiplier implements BitFieldOffset { + private static readonly OFFSET_MULTIPLIER_PREFIX = "#"; + private readonly offset: string; + + /** + * Creates an instance of BitOffsetMultiplier. + * + * @param offset - The offset in the array of bits, which will be multiplied by the encoding value to get the final + * bit index offset. + */ + constructor(offset: number) { + this.offset = `${BitOffsetMultiplier.OFFSET_MULTIPLIER_PREFIX}${offset.toString()}`; + } + + public toArg(): string { + return this.offset; + } +} + +/** + * Represents subcommands for the {@link BaseClient.bitfield|bitfield} or + * {@link BaseClient.bitfieldReadOnly|bitfieldReadOnly} commands. + */ +export interface BitFieldSubCommands { + /** + * Returns the subcommand as a list of string arguments to be used in the {@link BaseClient.bitfield|bitfield} or + * {@link BaseClient.bitfieldReadOnly|bitfieldReadOnly} commands. + * + * @returns The subcommand as a list of string arguments. + */ + toArgs(): string[]; +} + +/** + * Represents the "GET" subcommand for getting a value in the binary representation of the string stored in `key`. + */ +export class BitFieldGet implements BitFieldSubCommands { + private static readonly GET_COMMAND_STRING = "GET"; + private readonly encoding: BitEncoding; + private readonly offset: BitFieldOffset; + + /** + * Creates an instance of BitFieldGet. + * + * @param encoding - The bit encoding for the subcommand. + * @param offset - The offset in the array of bits from which to get the value. + */ + constructor(encoding: BitEncoding, offset: BitFieldOffset) { + this.encoding = encoding; + this.offset = offset; + } + + toArgs(): string[] { + return [ + BitFieldGet.GET_COMMAND_STRING, + this.encoding.toArg(), + this.offset.toArg(), + ]; + } +} + +/** + * Represents the "SET" subcommand for setting bits in the binary representation of the string stored in `key`. + */ +export class BitFieldSet implements BitFieldSubCommands { + private static readonly SET_COMMAND_STRING = "SET"; + private readonly encoding: BitEncoding; + private readonly offset: BitFieldOffset; + private readonly value: number; + + /** + * Creates an instance of BitFieldSet + * + * @param encoding - The bit encoding for the subcommand. + * @param offset - The offset in the array of bits where the value will be set. + * @param value - The value to set the bits in the binary value to. + */ + constructor(encoding: BitEncoding, offset: BitFieldOffset, value: number) { + this.encoding = encoding; + this.offset = offset; + this.value = value; + } + + toArgs(): string[] { + return [ + BitFieldSet.SET_COMMAND_STRING, + this.encoding.toArg(), + this.offset.toArg(), + this.value.toString(), + ]; + } +} + +/** + * Represents the "INCRBY" subcommand for increasing or decreasing bits in the binary representation of the string + * stored in `key`. + */ +export class BitFieldIncrBy implements BitFieldSubCommands { + private static readonly INCRBY_COMMAND_STRING = "INCRBY"; + private readonly encoding: BitEncoding; + private readonly offset: BitFieldOffset; + private readonly increment: number; + + /** + * Creates an instance of BitFieldIncrBy + * + * @param encoding - The bit encoding for the subcommand. + * @param offset - The offset in the array of bits where the value will be incremented. + * @param increment - The value to increment the bits in the binary value by. + */ + constructor( + encoding: BitEncoding, + offset: BitFieldOffset, + increment: number, + ) { + this.encoding = encoding; + this.offset = offset; + this.increment = increment; + } + + toArgs(): string[] { + return [ + BitFieldIncrBy.INCRBY_COMMAND_STRING, + this.encoding.toArg(), + this.offset.toArg(), + this.increment.toString(), + ]; + } +} + +/** + * Enumeration specifying bit overflow controls for the {@link BaseClient.bitfield|bitfield} command. + */ +export enum BitOverflowControl { + /** + * Performs modulo when overflows occur with unsigned encoding. When overflows occur with signed encoding, the value + * restarts at the most negative value. When underflows occur with signed encoding, the value restarts at the most + * positive value. + */ + WRAP = "WRAP", + /** + * Underflows remain set to the minimum value, and overflows remain set to the maximum value. + */ + SAT = "SAT", + /** + * Returns `None` when overflows occur. + */ + FAIL = "FAIL", +} + +/** + * Represents the "OVERFLOW" subcommand that determines the result of the "SET" or "INCRBY" + * {@link BaseClient.bitfield|bitfield} subcommands when an underflow or overflow occurs. + */ +export class BitFieldOverflow implements BitFieldSubCommands { + private static readonly OVERFLOW_COMMAND_STRING = "OVERFLOW"; + private readonly overflowControl: BitOverflowControl; + + /** + * Creates an instance of BitFieldOverflow. + * + * @param overflowControl - The desired overflow behavior. + */ + constructor(overflowControl: BitOverflowControl) { + this.overflowControl = overflowControl; + } + + toArgs(): string[] { + return [BitFieldOverflow.OVERFLOW_COMMAND_STRING, this.overflowControl]; + } +} + +/** + * @internal + */ +export function createBitField( + key: GlideString, + subcommands: BitFieldSubCommands[], + readOnly: boolean = false, +): command_request.Command { + const requestType = readOnly + ? RequestType.BitFieldReadOnly + : RequestType.BitField; + let args: GlideString[] = [key]; + + for (const subcommand of subcommands) { + args = args.concat(subcommand.toArgs()); + } + + return createCommand(requestType, args); +} + /** * @internal */ export function createHDel( - key: string, - fields: string[], + key: GlideString, + fields: GlideString[], ): command_request.Command { return createCommand(RequestType.HDel, [key].concat(fields)); } @@ -448,8 +818,8 @@ export function createHDel( * @internal */ export function createHMGet( - key: string, - fields: string[], + key: GlideString, + fields: GlideString[], ): command_request.Command { return createCommand(RequestType.HMGet, [key].concat(fields)); } @@ -458,8 +828,8 @@ export function createHMGet( * @internal */ export function createHExists( - key: string, - field: string, + key: GlideString, + field: GlideString, ): command_request.Command { return createCommand(RequestType.HExists, [key, field]); } @@ -467,7 +837,7 @@ export function createHExists( /** * @internal */ -export function createHGetAll(key: string): command_request.Command { +export function createHGetAll(key: GlideString): command_request.Command { return createCommand(RequestType.HGetAll, [key]); } @@ -475,20 +845,31 @@ export function createHGetAll(key: string): command_request.Command { * @internal */ export function createLPush( - key: string, - elements: string[], + key: GlideString, + elements: GlideString[], ): command_request.Command { return createCommand(RequestType.LPush, [key].concat(elements)); } +/** + * @internal + */ +export function createLPushX( + key: GlideString, + elements: GlideString[], +): command_request.Command { + return createCommand(RequestType.LPushX, [key].concat(elements)); +} + /** * @internal */ export function createLPop( - key: string, + key: GlideString, count?: number, ): command_request.Command { - const args: string[] = count == undefined ? [key] : [key, count.toString()]; + const args: GlideString[] = + count == undefined ? [key] : [key, count.toString()]; return createCommand(RequestType.LPop, args); } @@ -496,7 +877,7 @@ export function createLPop( * @internal */ export function createLRange( - key: string, + key: GlideString, start: number, end: number, ): command_request.Command { @@ -510,17 +891,67 @@ export function createLRange( /** * @internal */ -export function createLLen(key: string): command_request.Command { +export function createLLen(key: GlideString): command_request.Command { return createCommand(RequestType.LLen, [key]); } +/** + * Enumeration representing element popping or adding direction for the List Based Commands. + */ +export enum ListDirection { + /** + * Represents the option that elements should be popped from or added to the left side of a list. + */ + LEFT = "LEFT", + /** + * Represents the option that elements should be popped from or added to the right side of a list. + */ + RIGHT = "RIGHT", +} + +/** + * @internal + */ +export function createLMove( + source: GlideString, + destination: GlideString, + whereFrom: ListDirection, + whereTo: ListDirection, +): command_request.Command { + return createCommand(RequestType.LMove, [ + source, + destination, + whereFrom, + whereTo, + ]); +} + +/** + * @internal + */ +export function createBLMove( + source: GlideString, + destination: GlideString, + whereFrom: ListDirection, + whereTo: ListDirection, + timeout: number, +): command_request.Command { + return createCommand(RequestType.BLMove, [ + source, + destination, + whereFrom, + whereTo, + timeout.toString(), + ]); +} + /** * @internal */ export function createLSet( - key: string, + key: GlideString, index: number, - element: string, + element: GlideString, ): command_request.Command { return createCommand(RequestType.LSet, [key, index.toString(), element]); } @@ -529,7 +960,7 @@ export function createLSet( * @internal */ export function createLTrim( - key: string, + key: GlideString, start: number, end: number, ): command_request.Command { @@ -544,9 +975,9 @@ export function createLTrim( * @internal */ export function createLRem( - key: string, + key: GlideString, count: number, - element: string, + element: GlideString, ): command_request.Command { return createCommand(RequestType.LRem, [key, count.toString(), element]); } @@ -555,20 +986,31 @@ export function createLRem( * @internal */ export function createRPush( - key: string, - elements: string[], + key: GlideString, + elements: GlideString[], ): command_request.Command { return createCommand(RequestType.RPush, [key].concat(elements)); } +/** + * @internal + */ +export function createRPushX( + key: GlideString, + elements: GlideString[], +): command_request.Command { + return createCommand(RequestType.RPushX, [key].concat(elements)); +} + /** * @internal */ export function createRPop( - key: string, + key: GlideString, count?: number, ): command_request.Command { - const args: string[] = count == undefined ? [key] : [key, count.toString()]; + const args: GlideString[] = + count == undefined ? [key] : [key, count.toString()]; return createCommand(RequestType.RPop, args); } @@ -576,8 +1018,8 @@ export function createRPop( * @internal */ export function createSAdd( - key: string, - members: string[], + key: GlideString, + members: GlideString[], ): command_request.Command { return createCommand(RequestType.SAdd, [key].concat(members)); } @@ -586,8 +1028,8 @@ export function createSAdd( * @internal */ export function createSRem( - key: string, - members: string[], + key: GlideString, + members: GlideString[], ): command_request.Command { return createCommand(RequestType.SRem, [key].concat(members)); } @@ -595,18 +1037,35 @@ export function createSRem( /** * @internal */ -export function createSMembers(key: string): command_request.Command { - return createCommand(RequestType.SMembers, [key]); -} +export function createSScan( + key: GlideString, + cursor: GlideString, + options?: BaseScanOptions, +): command_request.Command { + let args: GlideString[] = [key, cursor]; + + if (options) { + args = args.concat(convertBaseScanOptionsToArgsArray(options)); + } + + return createCommand(RequestType.SScan, args); +} + +/** + * @internal + */ +export function createSMembers(key: GlideString): command_request.Command { + return createCommand(RequestType.SMembers, [key]); +} /** * * @internal */ export function createSMove( - source: string, - destination: string, - member: string, + source: GlideString, + destination: GlideString, + member: GlideString, ): command_request.Command { return createCommand(RequestType.SMove, [source, destination, member]); } @@ -614,23 +1073,40 @@ export function createSMove( /** * @internal */ -export function createSCard(key: string): command_request.Command { +export function createSCard(key: GlideString): command_request.Command { return createCommand(RequestType.SCard, [key]); } /** * @internal */ -export function createSInter(keys: string[]): command_request.Command { +export function createSInter(keys: GlideString[]): command_request.Command { return createCommand(RequestType.SInter, keys); } +/** + * @internal + */ +export function createSInterCard( + keys: GlideString[], + limit?: number, +): command_request.Command { + let args: GlideString[] = keys; + args.unshift(keys.length.toString()); + + if (limit != undefined) { + args = args.concat(["LIMIT", limit.toString()]); + } + + return createCommand(RequestType.SInterCard, args); +} + /** * @internal */ export function createSInterStore( - destination: string, - keys: string[], + destination: GlideString, + keys: GlideString[], ): command_request.Command { return createCommand(RequestType.SInterStore, [destination].concat(keys)); } @@ -638,7 +1114,7 @@ export function createSInterStore( /** * @internal */ -export function createSDiff(keys: string[]): command_request.Command { +export function createSDiff(keys: GlideString[]): command_request.Command { return createCommand(RequestType.SDiff, keys); } @@ -646,8 +1122,8 @@ export function createSDiff(keys: string[]): command_request.Command { * @internal */ export function createSDiffStore( - destination: string, - keys: string[], + destination: GlideString, + keys: GlideString[], ): command_request.Command { return createCommand(RequestType.SDiffStore, [destination].concat(keys)); } @@ -655,7 +1131,7 @@ export function createSDiffStore( /** * @internal */ -export function createSUnion(keys: string[]): command_request.Command { +export function createSUnion(keys: GlideString[]): command_request.Command { return createCommand(RequestType.SUnion, keys); } @@ -663,8 +1139,8 @@ export function createSUnion(keys: string[]): command_request.Command { * @internal */ export function createSUnionStore( - destination: string, - keys: string[], + destination: GlideString, + keys: GlideString[], ): command_request.Command { return createCommand(RequestType.SUnionStore, [destination].concat(keys)); } @@ -673,27 +1149,50 @@ export function createSUnionStore( * @internal */ export function createSIsMember( - key: string, - member: string, + key: GlideString, + member: GlideString, ): command_request.Command { return createCommand(RequestType.SIsMember, [key, member]); } +/** + * @internal + */ +export function createSMIsMember( + key: GlideString, + members: GlideString[], +): command_request.Command { + return createCommand(RequestType.SMIsMember, [key].concat(members)); +} + /** * @internal */ export function createSPop( - key: string, + key: GlideString, count?: number, ): command_request.Command { - const args: string[] = count == undefined ? [key] : [key, count.toString()]; + const args: GlideString[] = + count == undefined ? [key] : [key, count.toString()]; return createCommand(RequestType.SPop, args); } /** * @internal */ -export function createCustomCommand(args: string[]) { +export function createSRandMember( + key: GlideString, + count?: number, +): command_request.Command { + const args: GlideString[] = + count == undefined ? [key] : [key, count.toString()]; + return createCommand(RequestType.SRandMember, args); +} + +/** + * @internal + */ +export function createCustomCommand(args: GlideString[]) { return createCommand(RequestType.CustomCommand, args); } @@ -701,8 +1200,8 @@ export function createCustomCommand(args: string[]) { * @internal */ export function createHIncrBy( - key: string, - field: string, + key: GlideString, + field: GlideString, amount: number, ): command_request.Command { return createCommand(RequestType.HIncrBy, [key, field, amount.toString()]); @@ -712,8 +1211,8 @@ export function createHIncrBy( * @internal */ export function createHIncrByFloat( - key: string, - field: string, + key: GlideString, + field: GlideString, amount: number, ): command_request.Command { return createCommand(RequestType.HIncrByFloat, [ @@ -726,28 +1225,28 @@ export function createHIncrByFloat( /** * @internal */ -export function createHLen(key: string): command_request.Command { +export function createHLen(key: GlideString): command_request.Command { return createCommand(RequestType.HLen, [key]); } /** * @internal */ -export function createHVals(key: string): command_request.Command { +export function createHVals(key: GlideString): command_request.Command { return createCommand(RequestType.HVals, [key]); } /** * @internal */ -export function createExists(keys: string[]): command_request.Command { +export function createExists(keys: GlideString[]): command_request.Command { return createCommand(RequestType.Exists, keys); } /** * @internal */ -export function createUnlink(keys: string[]): command_request.Command { +export function createUnlink(keys: GlideString[]): command_request.Command { return createCommand(RequestType.Unlink, keys); } @@ -776,11 +1275,11 @@ export enum ExpireOptions { * @internal */ export function createExpire( - key: string, + key: GlideString, seconds: number, option?: ExpireOptions, ): command_request.Command { - const args: string[] = + const args = option == undefined ? [key, seconds.toString()] : [key, seconds.toString(), option]; @@ -791,26 +1290,33 @@ export function createExpire( * @internal */ export function createExpireAt( - key: string, + key: GlideString, unixSeconds: number, option?: ExpireOptions, ): command_request.Command { - const args: string[] = + const args = option == undefined ? [key, unixSeconds.toString()] : [key, unixSeconds.toString(), option]; return createCommand(RequestType.ExpireAt, args); } +/** + * @internal + */ +export function createExpireTime(key: GlideString): command_request.Command { + return createCommand(RequestType.ExpireTime, [key]); +} + /** * @internal */ export function createPExpire( - key: string, + key: GlideString, milliseconds: number, option?: ExpireOptions, ): command_request.Command { - const args: string[] = + const args = option == undefined ? [key, milliseconds.toString()] : [key, milliseconds.toString(), option]; @@ -821,11 +1327,11 @@ export function createPExpire( * @internal */ export function createPExpireAt( - key: string, + key: GlideString, unixMilliseconds: number, option?: ExpireOptions, ): command_request.Command { - const args: string[] = + const args = option == undefined ? [key, unixMilliseconds.toString()] : [key, unixMilliseconds.toString(), option]; @@ -835,25 +1341,40 @@ export function createPExpireAt( /** * @internal */ -export function createTTL(key: string): command_request.Command { +export function createPExpireTime(key: GlideString): command_request.Command { + return createCommand(RequestType.PExpireTime, [key]); +} + +/** + * @internal + */ +export function createTTL(key: GlideString): command_request.Command { return createCommand(RequestType.TTL, [key]); } +/** + * Options for updating elements of a sorted set key. + */ +export enum UpdateByScore { + /** Only update existing elements if the new score is less than the current score. */ + LESS_THAN = "LT", + /** Only update existing elements if the new score is greater than the current score. */ + GREATER_THAN = "GT", +} + export type ZAddOptions = { /** - * `onlyIfDoesNotExist` - Only add new elements. Don't update already existing - * elements. Equivalent to `NX` in the Redis API. `onlyIfExists` - Only update - * elements that already exist. Don't add new elements. Equivalent to `XX` in - * the Redis API. + * Options for handling existing members. + */ + conditionalChange?: ConditionalChange; + /** + * Options for updating scores. */ - conditionalChange?: "onlyIfExists" | "onlyIfDoesNotExist"; + updateOptions?: UpdateByScore; /** - * `scoreLessThanCurrent` - Only update existing elements if the new score is - * less than the current score. Equivalent to `LT` in the Redis API. - * `scoreGreaterThanCurrent` - Only update existing elements if the new score - * is greater than the current score. Equivalent to `GT` in the Redis API. + * Modify the return value from the number of new elements added, to the total number of elements changed. */ - updateOptions?: "scoreLessThanCurrent" | "scoreGreaterThanCurrent"; + changed?: boolean; }; /** @@ -863,32 +1384,36 @@ export function createZAdd( key: string, membersScoresMap: Record, options?: ZAddOptions, - changedOrIncr?: "CH" | "INCR", + incr: boolean = false, ): command_request.Command { let args = [key]; if (options) { - if (options.conditionalChange === "onlyIfExists") { - args.push("XX"); - } else if (options.conditionalChange === "onlyIfDoesNotExist") { - if (options.updateOptions) { + if (options.conditionalChange) { + if ( + options.conditionalChange === + ConditionalChange.ONLY_IF_DOES_NOT_EXIST && + options.updateOptions + ) { throw new Error( `The GT, LT, and NX options are mutually exclusive. Cannot choose both ${options.updateOptions} and NX.`, ); } - args.push("NX"); + args.push(options.conditionalChange); + } + + if (options.updateOptions) { + args.push(options.updateOptions); } - if (options.updateOptions === "scoreLessThanCurrent") { - args.push("LT"); - } else if (options.updateOptions === "scoreGreaterThanCurrent") { - args.push("GT"); + if (options.changed) { + args.push("CH"); } } - if (changedOrIncr) { - args.push(changedOrIncr); + if (incr) { + args.push("INCR"); } args = args.concat( @@ -903,7 +1428,7 @@ export function createZAdd( /** * `KeyWeight` - pair of variables represents a weighted key for the `ZINTERSTORE` and `ZUNIONSTORE` sorted sets commands. */ -export type KeyWeight = [string, number]; +export type KeyWeight = [GlideString, number]; /** * `AggregationType` - representing aggregation types for `ZINTERSTORE` and `ZUNIONSTORE` sorted set commands. */ @@ -913,34 +1438,84 @@ export type AggregationType = "SUM" | "MIN" | "MAX"; * @internal */ export function createZInterstore( - destination: string, - keys: string[] | KeyWeight[], + destination: GlideString, + keys: GlideString[] | KeyWeight[], aggregationType?: AggregationType, ): command_request.Command { - const args = createZCmdStoreArgs(destination, keys, aggregationType); + const args = createZCmdArgs(keys, { + aggregationType, + withScores: false, + destination, + }); return createCommand(RequestType.ZInterStore, args); } -function createZCmdStoreArgs( - destination: string, +/** + * @internal + */ +export function createZInter( + keys: GlideString[] | KeyWeight[], + aggregationType?: AggregationType, + withScores?: boolean, +): command_request.Command { + const args = createZCmdArgs(keys, { aggregationType, withScores }); + return createCommand(RequestType.ZInter, args); +} + +/** + * @internal + */ +export function createZUnion( keys: string[] | KeyWeight[], aggregationType?: AggregationType, -): string[] { - const args: string[] = [destination, keys.length.toString()]; + withScores?: boolean, +): command_request.Command { + const args = createZCmdArgs(keys, { aggregationType, withScores }); + return createCommand(RequestType.ZUnion, args); +} - if (typeof keys[0] === "string") { - args.push(...(keys as string[])); +/** + * @internal + * Helper function for Zcommands (ZInter, ZinterStore, ZUnion..) that arranges arguments in the server's required order. + */ +function createZCmdArgs( + keys: GlideString[] | KeyWeight[], + options: { + aggregationType?: AggregationType; + withScores?: boolean; + destination?: GlideString; + }, +): GlideString[] { + const args: GlideString[] = []; + + const destination = options.destination; + + if (destination) { + args.push(destination); + } + + args.push(keys.length.toString()); + + if (!Array.isArray(keys[0])) { + // KeyWeight is an array + args.push(...(keys as GlideString[])); } else { const weightsKeys = keys.map(([key]) => key); - args.push(...(weightsKeys as string[])); + args.push(...(weightsKeys as GlideString[])); const weights = keys.map(([, weight]) => weight.toString()); args.push("WEIGHTS", ...weights); } + const aggregationType = options.aggregationType; + if (aggregationType) { args.push("AGGREGATE", aggregationType); } + if (options.withScores) { + args.push("WITHSCORES"); + } + return args; } @@ -957,7 +1532,7 @@ export function createZRem( /** * @internal */ -export function createZCard(key: string): command_request.Command { +export function createZCard(key: GlideString): command_request.Command { return createCommand(RequestType.ZCard, [key]); } @@ -965,19 +1540,51 @@ export function createZCard(key: string): command_request.Command { * @internal */ export function createZInterCard( - keys: string[], + keys: GlideString[], limit?: number, ): command_request.Command { - let args: string[] = keys; + const args = keys; args.unshift(keys.length.toString()); if (limit != undefined) { - args = args.concat(["LIMIT", limit.toString()]); + args.push("LIMIT", limit.toString()); } return createCommand(RequestType.ZInterCard, args); } +/** + * @internal + */ +export function createZDiff(keys: GlideString[]): command_request.Command { + const args = keys; + args.unshift(keys.length.toString()); + return createCommand(RequestType.ZDiff, args); +} + +/** + * @internal + */ +export function createZDiffWithScores( + keys: GlideString[], +): command_request.Command { + const args = keys; + args.unshift(keys.length.toString()); + args.push("WITHSCORES"); + return createCommand(RequestType.ZDiff, args); +} + +/** + * @internal + */ +export function createZDiffStore( + destination: GlideString, + keys: GlideString[], +): command_request.Command { + const args = [destination, keys.length.toString(), ...keys]; + return createCommand(RequestType.ZDiffStore, args); +} + /** * @internal */ @@ -988,25 +1595,57 @@ export function createZScore( return createCommand(RequestType.ZScore, [key, member]); } -export type ScoreBoundary = +/** + * @internal + */ +export function createZUnionStore( + destination: string, + keys: string[] | KeyWeight[], + aggregationType?: AggregationType, +): command_request.Command { + const args = createZCmdArgs(keys, { destination, aggregationType }); + return createCommand(RequestType.ZUnionStore, args); +} + +/** + * @internal + */ +export function createZMScore( + key: string, + members: string[], +): command_request.Command { + return createCommand(RequestType.ZMScore, [key, ...members]); +} + +export enum InfBoundary { + /** + * Positive infinity bound. + */ + PositiveInfinity = "+", /** - * Positive infinity bound for sorted set. + * Negative infinity bound. */ - | `positiveInfinity` + NegativeInfinity = "-", +} + +/** + * Defines the boundaries of a range. + */ +export type Boundary = /** - * Negative infinity bound for sorted set. + * Represents an lower/upper boundary. */ - | `negativeInfinity` + | InfBoundary /** - * Represents a specific numeric score boundary in a sorted set. + * Represents a specific boundary. */ | { /** - * The score value. + * The comparison value. */ value: T; /** - * Whether the score value is inclusive. Defaults to True. + * Whether the value is inclusive. Defaults to `true`. */ isInclusive?: boolean; }; @@ -1034,11 +1673,11 @@ type SortedSetRange = { /** * The start boundary. */ - start: ScoreBoundary; + start: Boundary; /** * The stop boundary. */ - stop: ScoreBoundary; + stop: Boundary; /** * The limit argument for a range query. * Represents a limit argument for a range query in a sorted set to @@ -1063,31 +1702,50 @@ type SortedSetRange = { export type RangeByScore = SortedSetRange & { type: "byScore" }; export type RangeByLex = SortedSetRange & { type: "byLex" }; -/** - * Returns a string representation of a score boundary in Redis protocol format. - * @param score - The score boundary object containing value and inclusivity - * information. - * @param isLex - Indicates whether to return lexical representation for - * positive/negative infinity. - * @returns A string representation of the score boundary in Redis protocol - * format. - */ +/** Returns a string representation of a score boundary as a command argument. */ function getScoreBoundaryArg( - score: ScoreBoundary | ScoreBoundary, - isLex: boolean = false, + score: Boundary | Boundary, ): string { - if (score == "positiveInfinity") { - return isLex ? "+" : "+inf"; - } else if (score == "negativeInfinity") { - return isLex ? "-" : "-inf"; + if (typeof score === "string") { + // InfBoundary + return score + "inf"; } if (score.isInclusive == false) { return "(" + score.value.toString(); } - const value = isLex ? "[" + score.value.toString() : score.value.toString(); - return value; + return score.value.toString(); +} + +/** Returns a string representation of a lex boundary as a command argument. */ +function getLexBoundaryArg(score: Boundary | Boundary): string { + if (typeof score === "string") { + // InfBoundary + return score; + } + + if (score.isInclusive == false) { + return "(" + score.value; + } + + return "[" + score.value; +} + +/** Returns a string representation of a stream boundary as a command argument. */ +function getStreamBoundaryArg( + boundary: Boundary | Boundary, +): string { + if (typeof boundary === "string") { + // InfBoundary + return boundary; + } + + if (boundary.isInclusive == false) { + return "(" + boundary.value.toString(); + } + + return boundary.value.toString(); } function createZRangeArgs( @@ -1100,10 +1758,20 @@ function createZRangeArgs( if (typeof rangeQuery.start != "number") { rangeQuery = rangeQuery as RangeByScore | RangeByLex; - const isLex = rangeQuery.type == "byLex"; - args.push(getScoreBoundaryArg(rangeQuery.start, isLex)); - args.push(getScoreBoundaryArg(rangeQuery.stop, isLex)); - args.push(isLex == true ? "BYLEX" : "BYSCORE"); + + if (rangeQuery.type == "byLex") { + args.push( + getLexBoundaryArg(rangeQuery.start), + getLexBoundaryArg(rangeQuery.stop), + "BYLEX", + ); + } else { + args.push( + getScoreBoundaryArg(rangeQuery.start), + getScoreBoundaryArg(rangeQuery.stop), + "BYSCORE", + ); + } } else { args.push(rangeQuery.start.toString()); args.push(rangeQuery.stop.toString()); @@ -1132,13 +1800,15 @@ function createZRangeArgs( * @internal */ export function createZCount( - key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + key: GlideString, + minScore: Boundary, + maxScore: Boundary, ): command_request.Command { - const args = [key]; - args.push(getScoreBoundaryArg(minScore)); - args.push(getScoreBoundaryArg(maxScore)); + const args = [ + key, + getScoreBoundaryArg(minScore), + getScoreBoundaryArg(maxScore), + ]; return createCommand(RequestType.ZCount, args); } @@ -1169,14 +1839,30 @@ export function createZRangeWithScores( /** * @internal */ -export function createType(key: string): command_request.Command { +export function createZRangeStore( + destination: string, + source: string, + rangeQuery: RangeByIndex | RangeByScore | RangeByLex, + reverse: boolean = false, +): command_request.Command { + const args = [ + destination, + ...createZRangeArgs(source, rangeQuery, reverse, false), + ]; + return createCommand(RequestType.ZRangeStore, args); +} + +/** + * @internal + */ +export function createType(key: GlideString): command_request.Command { return createCommand(RequestType.Type, [key]); } /** * @internal */ -export function createStrlen(key: string): command_request.Command { +export function createStrlen(key: GlideString): command_request.Command { return createCommand(RequestType.Strlen, [key]); } @@ -1184,7 +1870,7 @@ export function createStrlen(key: string): command_request.Command { * @internal */ export function createLIndex( - key: string, + key: GlideString, index: number, ): command_request.Command { return createCommand(RequestType.LIndex, [key, index.toString()]); @@ -1208,10 +1894,10 @@ export enum InsertPosition { * @internal */ export function createLInsert( - key: string, + key: GlideString, position: InsertPosition, - pivot: string, - element: string, + pivot: GlideString, + element: GlideString, ): command_request.Command { return createCommand(RequestType.LInsert, [key, position, pivot, element]); } @@ -1241,14 +1927,14 @@ export function createZPopMax( /** * @internal */ -export function createEcho(message: string): command_request.Command { +export function createEcho(message: GlideString): command_request.Command { return createCommand(RequestType.Echo, [message]); } /** * @internal */ -export function createPTTL(key: string): command_request.Command { +export function createPTTL(key: GlideString): command_request.Command { return createCommand(RequestType.PTTL, [key]); } @@ -1267,24 +1953,52 @@ export function createZRemRangeByRank( ]); } +/** + * @internal + */ +export function createZRemRangeByLex( + key: string, + minLex: Boundary, + maxLex: Boundary, +): command_request.Command { + const args = [key, getLexBoundaryArg(minLex), getLexBoundaryArg(maxLex)]; + return createCommand(RequestType.ZRemRangeByLex, args); +} + /** * @internal */ export function createZRemRangeByScore( key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + minScore: Boundary, + maxScore: Boundary, ): command_request.Command { - const args = [key]; - args.push(getScoreBoundaryArg(minScore)); - args.push(getScoreBoundaryArg(maxScore)); + const args = [ + key, + getScoreBoundaryArg(minScore), + getScoreBoundaryArg(maxScore), + ]; return createCommand(RequestType.ZRemRangeByScore, args); } -export function createPersist(key: string): command_request.Command { +/** @internal */ +export function createPersist(key: GlideString): command_request.Command { return createCommand(RequestType.Persist, [key]); } +/** + * @internal + */ +export function createZLexCount( + key: string, + minLex: Boundary, + maxLex: Boundary, +): command_request.Command { + const args = [key, getLexBoundaryArg(minLex), getLexBoundaryArg(maxLex)]; + return createCommand(RequestType.ZLexCount, args); +} + +/** @internal */ export function createZRank( key: string, member: string, @@ -1345,7 +2059,7 @@ export type StreamAddOptions = { trim?: StreamTrimOptions; }; -function addTrimOptions(options: StreamTrimOptions, args: string[]) { +function addTrimOptions(options: StreamTrimOptions, args: GlideString[]) { if (options.method === "maxlen") { args.push("MAXLEN"); } else if (options.method === "minid") { @@ -1370,9 +2084,12 @@ function addTrimOptions(options: StreamTrimOptions, args: string[]) { } } +/** + * @internal + */ export function createXAdd( - key: string, - values: [string, string][], + key: GlideString, + values: [GlideString, GlideString][], options?: StreamAddOptions, ): command_request.Command { const args = [key]; @@ -1399,6 +2116,16 @@ export function createXAdd( return createCommand(RequestType.XAdd, args); } +/** + * @internal + */ +export function createXDel( + key: GlideString, + ids: GlideString[], +): command_request.Command { + return createCommand(RequestType.XDel, [key, ...ids]); +} + /** * @internal */ @@ -1414,159 +2141,1869 @@ export function createXTrim( /** * @internal */ -export function createTime(): command_request.Command { - return createCommand(RequestType.Time, []); +export function createXRange( + key: string, + start: Boundary, + end: Boundary, + count?: number, +): command_request.Command { + const args = [key, getStreamBoundaryArg(start), getStreamBoundaryArg(end)]; + + if (count !== undefined) { + args.push("COUNT"); + args.push(count.toString()); + } + + return createCommand(RequestType.XRange, args); } /** * @internal */ -export function createBRPop( - keys: string[], - timeout: number, +export function createXRevRange( + key: string, + start: Boundary, + end: Boundary, + count?: number, ): command_request.Command { - const args = [...keys, timeout.toString()]; - return createCommand(RequestType.BRPop, args); + const args = [key, getStreamBoundaryArg(start), getStreamBoundaryArg(end)]; + + if (count !== undefined) { + args.push("COUNT"); + args.push(count.toString()); + } + + return createCommand(RequestType.XRevRange, args); } /** * @internal */ -export function createBLPop( - keys: string[], - timeout: number, +export function createXGroupCreateConsumer( + key: GlideString, + groupName: GlideString, + consumerName: GlideString, ): command_request.Command { - const args = [...keys, timeout.toString()]; - return createCommand(RequestType.BLPop, args); + return createCommand(RequestType.XGroupCreateConsumer, [ + key, + groupName, + consumerName, + ]); } -export type StreamReadOptions = { - /** - * If set, the read request will block for the set amount of milliseconds or - * until the server has the required number of entries. Equivalent to `BLOCK` - * in the Redis API. - */ - block?: number; - /** - * The maximal number of elements requested. - * Equivalent to `COUNT` in the Redis API. - */ - count?: number; -}; - -function addReadOptions(options: StreamReadOptions, args: string[]) { - if (options.count !== undefined) { - args.push("COUNT"); - args.push(options.count.toString()); +/** + * @internal + */ +export function createXGroupDelConsumer( + key: GlideString, + groupName: GlideString, + consumerName: GlideString, +): command_request.Command { + return createCommand(RequestType.XGroupDelConsumer, [ + key, + groupName, + consumerName, + ]); +} + +/** + * @internal + */ +export function createTime(): command_request.Command { + return createCommand(RequestType.Time, []); +} + +/** + * @internal + */ +export function createPublish( + message: GlideString, + channel: GlideString, + sharded: boolean = false, +): command_request.Command { + const request = sharded ? RequestType.SPublish : RequestType.Publish; + return createCommand(request, [channel, message]); +} + +/** + * @internal + */ +export function createBRPop( + keys: GlideString[], + timeout: number, +): command_request.Command { + const args = [...keys, timeout.toString()]; + return createCommand(RequestType.BRPop, args); +} + +/** + * @internal + */ +export function createBLPop( + keys: GlideString[], + timeout: number, +): command_request.Command { + const args = [...keys, timeout.toString()]; + return createCommand(RequestType.BLPop, args); +} + +/** + * @internal + */ +export function createFCall( + func: GlideString, + keys: GlideString[], + args: GlideString[], +): command_request.Command { + const params: GlideString[] = [ + func, + keys.length.toString(), + ...keys, + ...args, + ]; + return createCommand(RequestType.FCall, params); +} + +/** + * @internal + */ +export function createFCallReadOnly( + func: GlideString, + keys: GlideString[], + args: GlideString[], +): command_request.Command { + const params: GlideString[] = [ + func, + keys.length.toString(), + ...keys, + ...args, + ]; + return createCommand(RequestType.FCallReadOnly, params); +} + +/** + * @internal + */ +export function createFunctionDelete( + libraryCode: GlideString, +): command_request.Command { + return createCommand(RequestType.FunctionDelete, [libraryCode]); +} + +/** + * @internal + */ +export function createFunctionFlush(mode?: FlushMode): command_request.Command { + if (mode) { + return createCommand(RequestType.FunctionFlush, [mode.toString()]); + } else { + return createCommand(RequestType.FunctionFlush, []); + } +} + +/** + * @internal + */ +export function createFunctionLoad( + libraryCode: GlideString, + replace?: boolean, +): command_request.Command { + const args = replace ? ["REPLACE", libraryCode] : [libraryCode]; + return createCommand(RequestType.FunctionLoad, args); +} + +/** Optional arguments for `FUNCTION LIST` command. */ +export type FunctionListOptions = { + /** A wildcard pattern for matching library names. */ + libNamePattern?: GlideString; + /** Specifies whether to request the library code from the server or not. */ + withCode?: boolean; +}; + +/** Type of the response of `FUNCTION LIST` command. */ +export type FunctionListResponse = Record< + string, + GlideString | Record[] +>[]; + +/** + * @internal + */ +export function createFunctionList( + options?: FunctionListOptions, +): command_request.Command { + const args: GlideString[] = []; + + if (options) { + if (options.libNamePattern) { + args.push("LIBRARYNAME", options.libNamePattern); + } + + if (options.withCode) { + args.push("WITHCODE"); + } + } + + return createCommand(RequestType.FunctionList, args); +} + +/** Response for `FUNCTION STATS` command on a single node. + * The response is a map with 2 keys: + * 1. Information about the current running function/script (or null if none). + * 2. Details about the execution engines. + */ +export type FunctionStatsSingleResponse = Record< + string, + | null + | Record // Running function/script information + | Record> // Execution engines information +>; + +/** Full response for `FUNCTION STATS` command across multiple nodes. + * It maps node addresses to the per-node response. + */ +export type FunctionStatsFullResponse = Record< + string, // Node address + FunctionStatsSingleResponse +>; + +/** @internal */ +export function createFunctionStats(): command_request.Command { + return createCommand(RequestType.FunctionStats, []); +} + +/** @internal */ +export function createFunctionKill(): command_request.Command { + return createCommand(RequestType.FunctionKill, []); +} + +/** @internal */ +export function createFunctionDump(): command_request.Command { + return createCommand(RequestType.FunctionDump, []); +} + +/** + * Option for `FUNCTION RESTORE` command: {@link GlideClient.functionRestore} and + * {@link GlideClusterClient.functionRestore}. + * + * @see {@link https://valkey.io/commands/function-restore/"|valkey.io} for more details. + */ +export enum FunctionRestorePolicy { + /** + * Appends the restored libraries to the existing libraries and aborts on collision. This is the + * default policy. + */ + APPEND = "APPEND", + /** Deletes all existing libraries before restoring the payload. */ + FLUSH = "FLUSH", + /** + * Appends the restored libraries to the existing libraries, replacing any existing ones in case + * of name collisions. Note that this policy doesn't prevent function name collisions, only + * libraries. + */ + REPLACE = "REPLACE", +} + +/** @internal */ +export function createFunctionRestore( + data: Buffer, + policy?: FunctionRestorePolicy, +): command_request.Command { + return createCommand( + RequestType.FunctionRestore, + policy ? [data, policy] : [data], + ); +} + +/** + * Represents offsets specifying a string interval to analyze in the {@link BaseClient.bitcount|bitcount} command. The offsets are + * zero-based indexes, with `0` being the first index of the string, `1` being the next index and so on. + * The offsets can also be negative numbers indicating offsets starting at the end of the string, with `-1` being + * the last index of the string, `-2` being the penultimate, and so on. + * + * See https://valkey.io/commands/bitcount/ for more details. + */ +export type BitOffsetOptions = { + /** The starting offset index. */ + start: number; + /** The ending offset index. */ + end: number; + /** + * The index offset type. This option can only be specified if you are using server version 7.0.0 or above. + * Could be either {@link BitmapIndexType.BYTE} or {@link BitmapIndexType.BIT}. + * If no index type is provided, the indexes will be assumed to be byte indexes. + */ + indexType?: BitmapIndexType; +}; + +/** + * @internal + */ +export function createBitCount( + key: GlideString, + options?: BitOffsetOptions, +): command_request.Command { + const args = [key]; + + if (options) { + args.push(options.start.toString()); + args.push(options.end.toString()); + if (options.indexType) args.push(options.indexType); + } + + return createCommand(RequestType.BitCount, args); +} + +/** + * Enumeration specifying if index arguments are BYTE indexes or BIT indexes. + * Can be specified in {@link BitOffsetOptions}, which is an optional argument to the {@link BaseClient.bitcount|bitcount} command. + * Can also be specified as an optional argument to the {@link BaseClient.bitposInverval|bitposInterval} command. + * + * since - Valkey version 7.0.0. + */ +export enum BitmapIndexType { + /** Specifies that provided indexes are byte indexes. */ + BYTE = "BYTE", + /** Specifies that provided indexes are bit indexes. */ + BIT = "BIT", +} + +/** + * @internal + */ +export function createBitPos( + key: GlideString, + bit: number, + start?: number, + end?: number, + indexType?: BitmapIndexType, +): command_request.Command { + const args: GlideString[] = [key, bit.toString()]; + + if (start !== undefined) { + args.push(start.toString()); + } + + if (end !== undefined) { + args.push(end.toString()); + } + + if (indexType) { + args.push(indexType); + } + + return createCommand(RequestType.BitPos, args); +} + +/** + * Defines flushing mode for {@link GlideClient.flushall}, {@link GlideClusterClient.flushall}, + * {@link GlideClient.functionFlush}, {@link GlideClusterClient.functionFlush}, + * {@link GlideClient.flushdb} and {@link GlideClusterClient.flushdb} commands. + * + * See https://valkey.io/commands/flushall/ and https://valkey.io/commands/flushdb/ for details. + */ +export enum FlushMode { + /** + * Flushes synchronously. + * + * since Valkey version 6.2.0. + */ + SYNC = "SYNC", + /** Flushes asynchronously. */ + ASYNC = "ASYNC", +} + +/** Optional arguments for {@link BaseClient.xread|xread} command. */ +export type StreamReadOptions = { + /** + * If set, the read request will block for the set amount of milliseconds or + * until the server has the required number of entries. A value of `0` will block indefinitely. + * Equivalent to `BLOCK` in the Redis API. + */ + block?: number; + /** + * The maximal number of elements requested. + * Equivalent to `COUNT` in the Redis API. + */ + count?: number; +}; + +/** Optional arguments for {@link BaseClient.xreadgroup|xreadgroup} command. */ +export type StreamReadGroupOptions = StreamReadOptions & { + /** + * If set, messages are not added to the Pending Entries List (PEL). This is equivalent to + * acknowledging the message when it is read. + */ + noAck?: boolean; +}; + +/** @internal */ +function addReadOptions(options?: StreamReadOptions): string[] { + const args = []; + + if (options?.count !== undefined) { + args.push("COUNT"); + args.push(options.count.toString()); + } + + if (options?.block !== undefined) { + args.push("BLOCK"); + args.push(options.block.toString()); + } + + return args; +} + +/** @internal */ +function addStreamsArgs(keys_and_ids: Record): string[] { + return [ + "STREAMS", + ...Object.keys(keys_and_ids), + ...Object.values(keys_and_ids), + ]; +} + +/** + * @internal + */ +export function createXRead( + keys_and_ids: Record, + options?: StreamReadOptions, +): command_request.Command { + const args = addReadOptions(options); + args.push(...addStreamsArgs(keys_and_ids)); + + return createCommand(RequestType.XRead, args); +} + +/** @internal */ +export function createXReadGroup( + group: string, + consumer: string, + keys_and_ids: Record, + options?: StreamReadGroupOptions, +): command_request.Command { + const args: string[] = ["GROUP", group, consumer]; + + if (options) { + args.push(...addReadOptions(options)); + if (options.noAck) args.push("NOACK"); + } + + args.push(...addStreamsArgs(keys_and_ids)); + + return createCommand(RequestType.XReadGroup, args); +} + +/** + * Represents a the return type for XInfo Stream in the response + */ +export type ReturnTypeXinfoStream = { + [key: string]: + | StreamEntries + | Record[]>[]; +}; + +/** + * Represents an array of Stream Entires in the response + */ +export type StreamEntries = string | number | (string | number | string[])[][]; + +/** + * @internal + */ +export function createXInfoStream( + key: string, + options: boolean | number, +): command_request.Command { + const args: string[] = [key]; + + if (options != false) { + args.push("FULL"); + + if (typeof options === "number") { + args.push("COUNT"); + args.push(options.toString()); + } + } + + return createCommand(RequestType.XInfoStream, args); +} + +/** @internal */ +export function createXInfoGroups(key: string): command_request.Command { + return createCommand(RequestType.XInfoGroups, [key]); +} + +/** + * @internal + */ +export function createXLen(key: string): command_request.Command { + return createCommand(RequestType.XLen, [key]); +} + +/** Optional arguments for {@link BaseClient.xpendingWithOptions|xpending}. */ +export type StreamPendingOptions = { + /** Filter pending entries by their idle time - in milliseconds. Available since Valkey 6.2.0. */ + minIdleTime?: number; + /** Starting stream ID bound for range. Exclusive range is available since Valkey 6.2.0. */ + start: Boundary; + /** Ending stream ID bound for range. Exclusive range is available since Valkey 6.2.0. */ + end: Boundary; + /** Limit the number of messages returned. */ + count: number; + /** Filter pending entries by consumer. */ + consumer?: string; +}; + +/** @internal */ +export function createXPending( + key: string, + group: string, + options?: StreamPendingOptions, +): command_request.Command { + const args = [key, group]; + + if (options) { + if (options.minIdleTime !== undefined) + args.push("IDLE", options.minIdleTime.toString()); + args.push( + getStreamBoundaryArg(options.start), + getStreamBoundaryArg(options.end), + options.count.toString(), + ); + if (options.consumer) args.push(options.consumer); + } + + return createCommand(RequestType.XPending, args); +} + +/** @internal */ +export function createXInfoConsumers( + key: string, + group: string, +): command_request.Command { + return createCommand(RequestType.XInfoConsumers, [key, group]); +} + +/** Optional parameters for {@link BaseClient.xclaim|xclaim} command. */ +export type StreamClaimOptions = { + /** + * Set the idle time (last time it was delivered) of the message in milliseconds. If `idle` + * is not specified, an `idle` of `0` is assumed, that is, the time count is reset + * because the message now has a new owner trying to process it. + */ + idle?: number; // in milliseconds + + /** + * This is the same as {@link idle} but instead of a relative amount of milliseconds, it sets the + * idle time to a specific Unix time (in milliseconds). This is useful in order to rewrite the AOF + * file generating `XCLAIM` commands. + */ + idleUnixTime?: number; // in unix-time milliseconds + + /** + * Set the retry counter to the specified value. This counter is incremented every time a message + * is delivered again. Normally {@link BaseClient.xclaim|xclaim} does not alter this counter, + * which is just served to clients when the {@link BaseClient.xpending|xpending} command is called: + * this way clients can detect anomalies, like messages that are never processed for some reason + * after a big number of delivery attempts. + */ + retryCount?: number; + + /** + * Creates the pending message entry in the PEL even if certain specified IDs are not already in + * the PEL assigned to a different client. However, the message must exist in the stream, + * otherwise the IDs of non-existing messages are ignored. + */ + isForce?: boolean; +}; + +/** @internal */ +export function createXClaim( + key: GlideString, + group: GlideString, + consumer: GlideString, + minIdleTime: number, + ids: GlideString[], + options?: StreamClaimOptions, + justId?: boolean, +): command_request.Command { + const args = [key, group, consumer, minIdleTime.toString(), ...ids]; + + if (options) { + if (options.idle !== undefined) + args.push("IDLE", options.idle.toString()); + if (options.idleUnixTime !== undefined) + args.push("TIME", options.idleUnixTime.toString()); + if (options.retryCount !== undefined) + args.push("RETRYCOUNT", options.retryCount.toString()); + if (options.isForce) args.push("FORCE"); + } + + if (justId) args.push("JUSTID"); + return createCommand(RequestType.XClaim, args); +} + +/** @internal */ +export function createXAutoClaim( + key: GlideString, + group: GlideString, + consumer: GlideString, + minIdleTime: number, + start: GlideString, + count?: number, + justId?: boolean, +): command_request.Command { + const args = [ + key, + group, + consumer, + minIdleTime.toString(), + start.toString(), + ]; + if (count !== undefined) args.push("COUNT", count.toString()); + if (justId) args.push("JUSTID"); + return createCommand(RequestType.XAutoClaim, args); +} + +/** + * Optional arguments for {@link BaseClient.xgroupCreate|xgroupCreate}. + * + * See https://valkey.io/commands/xgroup-create/ for more details. + */ +export type StreamGroupOptions = { + /** + * If `true`and the stream doesn't exist, creates a new stream with a length of `0`. + */ + mkStream?: boolean; + /** + * An arbitrary ID (that isn't the first ID, last ID, or the zero `"0-0"`. Use it to + * find out how many entries are between the arbitrary ID (excluding it) and the stream's last + * entry. + * + * since Valkey version 7.0.0. + */ + entriesRead?: string; +}; + +/** + * @internal + */ +export function createXGroupCreate( + key: GlideString, + groupName: GlideString, + id: GlideString, + options?: StreamGroupOptions, +): command_request.Command { + const args: GlideString[] = [key, groupName, id]; + + if (options) { + if (options.mkStream) { + args.push("MKSTREAM"); + } + + if (options.entriesRead) { + args.push("ENTRIESREAD"); + args.push(options.entriesRead); + } + } + + return createCommand(RequestType.XGroupCreate, args); +} + +/** + * @internal + */ +export function createXGroupDestroy( + key: GlideString, + groupName: GlideString, +): command_request.Command { + return createCommand(RequestType.XGroupDestroy, [key, groupName]); +} + +/** + * @internal + */ +export function createRename( + key: GlideString, + newKey: GlideString, +): command_request.Command { + return createCommand(RequestType.Rename, [key, newKey]); +} + +/** + * @internal + */ +export function createRenameNX( + key: GlideString, + newKey: GlideString, +): command_request.Command { + return createCommand(RequestType.RenameNX, [key, newKey]); +} + +/** + * @internal + */ +export function createPfAdd( + key: GlideString, + elements: GlideString[], +): command_request.Command { + const args = [key, ...elements]; + return createCommand(RequestType.PfAdd, args); +} + +/** + * @internal + */ +export function createPfCount(keys: GlideString[]): command_request.Command { + return createCommand(RequestType.PfCount, keys); +} + +/** + * @internal + */ +export function createPfMerge( + destination: GlideString, + sourceKey: GlideString[], +): command_request.Command { + return createCommand(RequestType.PfMerge, [destination, ...sourceKey]); +} + +/** + * @internal + */ +export function createObjectEncoding( + key: GlideString, +): command_request.Command { + return createCommand(RequestType.ObjectEncoding, [key]); +} + +/** + * @internal + */ +export function createObjectFreq(key: GlideString): command_request.Command { + return createCommand(RequestType.ObjectFreq, [key]); +} + +/** + * @internal + */ +export function createObjectIdletime( + key: GlideString, +): command_request.Command { + return createCommand(RequestType.ObjectIdleTime, [key]); +} + +/** + * @internal + */ +export function createObjectRefcount( + key: GlideString, +): command_request.Command { + return createCommand(RequestType.ObjectRefCount, [key]); +} + +/** Additional parameters for `LOLWUT` command. */ +export type LolwutOptions = { + /** + * An optional argument that can be used to specify the version of computer art to generate. + */ + version?: number; + /** + * An optional argument that can be used to specify the output: + * - For version `5`, those are length of the line, number of squares per row, and number of squares per column. + * - For version `6`, those are number of columns and number of lines. + */ + parameters?: number[]; +}; + +/** + * @internal + */ +export function createLolwut(options?: LolwutOptions): command_request.Command { + const args: string[] = []; + + if (options) { + if (options.version !== undefined) { + args.push("VERSION", options.version.toString()); + } + + if (options.parameters !== undefined) { + args.push(...options.parameters.map((param) => param.toString())); + } + } + + return createCommand(RequestType.Lolwut, args); +} + +/** + * @internal + */ +export function createFlushAll(mode?: FlushMode): command_request.Command { + if (mode) { + return createCommand(RequestType.FlushAll, [mode.toString()]); + } else { + return createCommand(RequestType.FlushAll, []); + } +} + +/** + * @internal + */ +export function createFlushDB(mode?: FlushMode): command_request.Command { + if (mode) { + return createCommand(RequestType.FlushDB, [mode.toString()]); + } else { + return createCommand(RequestType.FlushDB, []); + } +} + +/** + * @internal + */ +export function createCopy( + source: GlideString, + destination: GlideString, + options?: { destinationDB?: number; replace?: boolean }, +): command_request.Command { + let args = [source, destination]; + + if (options) { + if (options.destinationDB !== undefined) { + args = args.concat("DB", options.destinationDB.toString()); + } + + if (options.replace) { + args.push("REPLACE"); + } + } + + return createCommand(RequestType.Copy, args); +} + +/** + * @internal + */ +export function createMove( + key: GlideString, + dbIndex: number, +): command_request.Command { + return createCommand(RequestType.Move, [key, dbIndex.toString()]); +} + +/** + * @internal + */ +export function createDump(key: GlideString): command_request.Command { + return createCommand(RequestType.Dump, [key]); +} + +/** + * Optional arguments for `RESTORE` command. + * + * @See {@link https://valkey.io/commands/restore/|valkey.io} for details. + * @remarks `IDLETIME` and `FREQ` modifiers cannot be set at the same time. + */ +export type RestoreOptions = { + /** + * Set to `true` to replace the key if it exists. + */ + replace?: boolean; + /** + * Set to `true` to specify that `ttl` argument of {@link BaseClient.restore} represents + * an absolute Unix timestamp (in milliseconds). + */ + absttl?: boolean; + /** + * Set the `IDLETIME` option with object idletime to the given key. + */ + idletime?: number; + /** + * Set the `FREQ` option with object frequency to the given key. + */ + frequency?: number; +}; + +/** + * @internal + */ +export function createRestore( + key: GlideString, + ttl: number, + value: GlideString, + options?: RestoreOptions, +): command_request.Command { + const args: GlideString[] = [key, ttl.toString(), value]; + + if (options) { + if (options.idletime !== undefined && options.frequency !== undefined) { + throw new Error( + `syntax error: both IDLETIME and FREQ cannot be set at the same time.`, + ); + } + + if (options.replace) { + args.push("REPLACE"); + } + + if (options.absttl) { + args.push("ABSTTL"); + } + + if (options.idletime !== undefined) { + args.push("IDLETIME", options.idletime.toString()); + } + + if (options.frequency !== undefined) { + args.push("FREQ", options.frequency.toString()); + } + } + + return createCommand(RequestType.Restore, args); +} + +/** + * Optional arguments to LPOS command. + * + * See https://valkey.io/commands/lpos/ for more details. + */ +export type LPosOptions = { + /** The rank of the match to return. */ + rank?: number; + /** The specific number of matching indices from a list. */ + count?: number; + /** The maximum number of comparisons to make between the element and the items in the list. */ + maxLength?: number; +}; + +/** + * @internal + */ +export function createLPos( + key: GlideString, + element: GlideString, + options?: LPosOptions, +): command_request.Command { + const args: GlideString[] = [key, element]; + + if (options) { + if (options.rank !== undefined) { + args.push("RANK"); + args.push(options.rank.toString()); + } + + if (options.count !== undefined) { + args.push("COUNT"); + args.push(options.count.toString()); + } + + if (options.maxLength !== undefined) { + args.push("MAXLEN"); + args.push(options.maxLength.toString()); + } + } + + return createCommand(RequestType.LPos, args); +} + +/** + * @internal + */ +export function createDBSize(): command_request.Command { + return createCommand(RequestType.DBSize, []); +} + +/** + * An optional condition to the {@link BaseClient.geoadd | geoadd}, + * {@link BaseClient.zadd | zadd} and {@link BaseClient.set | set} commands. + */ +export enum ConditionalChange { + /** + * Only update elements that already exist. Don't add new elements. Equivalent to `XX` in the Valkey API. + */ + ONLY_IF_EXISTS = "XX", + + /** + * Only add new elements. Don't update already existing elements. Equivalent to `NX` in the Valkey API. + */ + ONLY_IF_DOES_NOT_EXIST = "NX", +} + +/** + * Represents a geographic position defined by longitude and latitude. + * The exact limits, as specified by `EPSG:900913 / EPSG:3785 / OSGEO:41001` are the + * following: + * + * Valid longitudes are from `-180` to `180` degrees. + * Valid latitudes are from `-85.05112878` to `85.05112878` degrees. + */ +export type GeospatialData = { + /** The longitude coordinate. */ + longitude: number; + /** The latitude coordinate. */ + latitude: number; +}; + +/** + * Optional arguments for the GeoAdd command. + * + * See https://valkey.io/commands/geoadd/ for more details. + */ +export type GeoAddOptions = { + /** Options for handling existing members. See {@link ConditionalChange}. */ + updateMode?: ConditionalChange; + /** If `true`, returns the count of changed elements instead of new elements added. */ + changed?: boolean; +}; + +/** + * @internal + */ +export function createGeoAdd( + key: GlideString, + membersToGeospatialData: Map, + options?: GeoAddOptions, +): command_request.Command { + let args: GlideString[] = [key]; + + if (options) { + if (options.updateMode) { + args.push(options.updateMode); + } + + if (options.changed) { + args.push("CH"); + } + } + + membersToGeospatialData.forEach((coord, member) => { + args = args.concat( + coord.longitude.toString(), + coord.latitude.toString(), + member, + ); + }); + return createCommand(RequestType.GeoAdd, args); +} + +/** Enumeration representing distance units options. */ +export enum GeoUnit { + /** Represents distance in meters. */ + METERS = "m", + /** Represents distance in kilometers. */ + KILOMETERS = "km", + /** Represents distance in miles. */ + MILES = "mi", + /** Represents distance in feet. */ + FEET = "ft", +} + +/** + * @internal + */ +export function createGeoPos( + key: GlideString, + members: GlideString[], +): command_request.Command { + return createCommand(RequestType.GeoPos, [key].concat(members)); +} + +/** + * @internal + */ +export function createGeoDist( + key: GlideString, + member1: GlideString, + member2: GlideString, + geoUnit?: GeoUnit, +): command_request.Command { + const args = [key, member1, member2]; + + if (geoUnit) { + args.push(geoUnit); + } + + return createCommand(RequestType.GeoDist, args); +} + +/** + * @internal + */ +export function createGeoHash( + key: GlideString, + members: GlideString[], +): command_request.Command { + const args = [key].concat(members); + return createCommand(RequestType.GeoHash, args); +} + +/** + * Optional parameters for {@link BaseClient.geosearch|geosearch} command which defines what should be included in the + * search results and how results should be ordered and limited. + */ +export type GeoSearchResultOptions = GeoSearchCommonResultOptions & { + /** Include the coordinate of the returned items. */ + withCoord?: boolean; + /** + * Include the distance of the returned items from the specified center point. + * The distance is returned in the same unit as specified for the `searchBy` argument. + */ + withDist?: boolean; + /** Include the geohash of the returned items. */ + withHash?: boolean; +}; + +/** + * Optional parameters for {@link BaseClient.geosearchstore|geosearchstore} command which defines what should be included in the + * search results and how results should be ordered and limited. + */ +export type GeoSearchStoreResultOptions = GeoSearchCommonResultOptions & { + /** + * Determines what is stored as the sorted set score. Defaults to `false`. + * - If set to `false`, the geohash of the location will be stored as the sorted set score. + * - If set to `true`, the distance from the center of the shape (circle or box) will be stored as the sorted set score. The distance is represented as a floating-point number in the same unit specified for that shape. + */ + storeDist?: boolean; +}; + +type GeoSearchCommonResultOptions = { + /** Indicates the order the result should be sorted in. */ + sortOrder?: SortOrder; + /** Indicates the number of matches the result should be limited to. */ + count?: number; + /** Whether to allow returning as enough matches are found. This requires `count` parameter to be set. */ + isAny?: boolean; +}; + +/** Defines the sort order for nested results. */ +export enum SortOrder { + /** Sort by ascending order. */ + ASC = "ASC", + /** Sort by descending order. */ + DESC = "DESC", +} + +export type GeoSearchShape = GeoCircleShape | GeoBoxShape; + +/** Circle search shape defined by the radius value and measurement unit. */ +export type GeoCircleShape = { + /** The radius to search by. */ + radius: number; + /** The measurement unit of the radius. */ + unit: GeoUnit; +}; + +/** Rectangle search shape defined by the width and height and measurement unit. */ +export type GeoBoxShape = { + /** The width of the rectangle to search by. */ + width: number; + /** The height of the rectangle to search by. */ + height: number; + /** The measurement unit of the width and height. */ + unit: GeoUnit; +}; + +export type SearchOrigin = CoordOrigin | MemberOrigin; + +/** The search origin represented by a {@link GeospatialData} position. */ +export type CoordOrigin = { + /** The pivot location to search from. */ + position: GeospatialData; +}; + +/** The search origin represented by an existing member. */ +export type MemberOrigin = { + /** Member (location) name stored in the sorted set to use as a search pivot. */ + member: GlideString; +}; + +/** @internal */ +export function createGeoSearch( + key: GlideString, + searchFrom: SearchOrigin, + searchBy: GeoSearchShape, + resultOptions?: GeoSearchResultOptions, +): command_request.Command { + const args = [key].concat( + convertGeoSearchOptionsToArgs(searchFrom, searchBy, resultOptions), + ); + return createCommand(RequestType.GeoSearch, args); +} + +/** @internal */ +export function createGeoSearchStore( + destination: GlideString, + source: GlideString, + searchFrom: SearchOrigin, + searchBy: GeoSearchShape, + resultOptions?: GeoSearchStoreResultOptions, +): command_request.Command { + const args = [destination, source].concat( + convertGeoSearchOptionsToArgs(searchFrom, searchBy, resultOptions), + ); + return createCommand(RequestType.GeoSearchStore, args); +} + +function convertGeoSearchOptionsToArgs( + searchFrom: SearchOrigin, + searchBy: GeoSearchShape, + resultOptions?: GeoSearchCommonResultOptions, +): GlideString[] { + let args: GlideString[] = []; + + if ("position" in searchFrom) { + args = args.concat( + "FROMLONLAT", + searchFrom.position.longitude.toString(), + searchFrom.position.latitude.toString(), + ); + } else { + args = args.concat("FROMMEMBER", searchFrom.member); + } + + if ("radius" in searchBy) { + args = args.concat( + "BYRADIUS", + searchBy.radius.toString(), + searchBy.unit, + ); + } else { + args = args.concat( + "BYBOX", + searchBy.width.toString(), + searchBy.height.toString(), + searchBy.unit, + ); + } + + if (resultOptions) { + if ( + "withCoord" in resultOptions && + (resultOptions as GeoSearchResultOptions).withCoord + ) + args.push("WITHCOORD"); + if ( + "withDist" in resultOptions && + (resultOptions as GeoSearchResultOptions).withDist + ) + args.push("WITHDIST"); + if ( + "withHash" in resultOptions && + (resultOptions as GeoSearchResultOptions).withHash + ) + args.push("WITHHASH"); + if ( + "storeDist" in resultOptions && + (resultOptions as GeoSearchStoreResultOptions).storeDist + ) + args.push("STOREDIST"); + + if (resultOptions.count) { + args.push("COUNT", resultOptions.count?.toString()); + + if (resultOptions.isAny) args.push("ANY"); + } + + if (resultOptions.sortOrder) args.push(resultOptions.sortOrder); + } + + return args; +} + +/** + * @internal + */ +export function createZRevRank( + key: string, + member: string, +): command_request.Command { + return createCommand(RequestType.ZRevRank, [key, member]); +} + +/** + * @internal + */ +export function createZRevRankWithScore( + key: string, + member: string, +): command_request.Command { + return createCommand(RequestType.ZRevRank, [key, member, "WITHSCORE"]); +} + +/** + * Mandatory option for zmpop. + * Defines which elements to pop from the sorted set. + */ +export enum ScoreFilter { + /** Pop elements with the highest scores. */ + MAX = "MAX", + /** Pop elements with the lowest scores. */ + MIN = "MIN", +} + +/** + * @internal + */ +export function createZMPop( + keys: string[], + modifier: ScoreFilter, + count?: number, +): command_request.Command { + const args: string[] = [keys.length.toString()].concat(keys); + args.push(modifier); + + if (count !== undefined) { + args.push("COUNT"); + args.push(count.toString()); } - if (options.block !== undefined) { - args.push("BLOCK"); - args.push(options.block.toString()); + return createCommand(RequestType.ZMPop, args); +} + +/** + * @internal + */ +export function createBZMPop( + keys: GlideString[], + modifier: ScoreFilter, + timeout: number, + count?: number, +): command_request.Command { + const args = [ + timeout.toString(), + keys.length.toString(), + ...keys, + modifier, + ]; + + if (count !== undefined) { + args.push("COUNT"); + args.push(count.toString()); } + + return createCommand(RequestType.BZMPop, args); +} + +/** + * @internal + */ +export function createZIncrBy( + key: GlideString, + increment: number, + member: GlideString, +): command_request.Command { + return createCommand(RequestType.ZIncrBy, [ + key, + increment.toString(), + member, + ]); +} + +/** + * Optional arguments to {@link GlideClient.sort|sort}, {@link GlideClient.sortStore|sortStore} and {@link GlideClient.sortReadOnly|sortReadOnly} commands. + * + * See https://valkey.io/commands/sort/ for more details. + */ +export type SortOptions = SortBaseOptions & { + /** + * A pattern to sort by external keys instead of by the elements stored at the key themselves. The + * pattern should contain an asterisk (*) as a placeholder for the element values, where the value + * from the key replaces the asterisk to create the key name. For example, if `key` + * contains IDs of objects, `byPattern` can be used to sort these IDs based on an + * attribute of the objects, like their weights or timestamps. + */ + byPattern?: GlideString; + + /** + * A pattern used to retrieve external keys' values, instead of the elements at `key`. + * The pattern should contain an asterisk (`*`) as a placeholder for the element values, where the + * value from `key` replaces the asterisk to create the `key` name. This + * allows the sorted elements to be transformed based on the related keys values. For example, if + * `key` contains IDs of users, `getPatterns` can be used to retrieve + * specific attributes of these users, such as their names or email addresses. E.g., if + * `getPatterns` is `name_*`, the command will return the values of the keys + * `name_` for each sorted element. Multiple `getPatterns` + * arguments can be provided to retrieve multiple attributes. The special value `#` can + * be used to include the actual element from `key` being sorted. If not provided, only + * the sorted elements themselves are returned. + */ + getPatterns?: GlideString[]; +}; + +type SortBaseOptions = { + /** + * Limiting the range of the query by setting offset and result count. See {@link Limit} class for + * more information. + */ + limit?: Limit; + + /** Options for sorting order of elements. */ + orderBy?: SortOrder; + + /** + * When `true`, sorts elements lexicographically. When `false` (default), + * sorts elements numerically. Use this when the list, set, or sorted set contains string values + * that cannot be converted into double precision floating point numbers. + */ + isAlpha?: boolean; +}; + +/** + * Optional arguments to {@link GlideClusterClient.sort|sort}, {@link GlideClusterClient.sortStore|sortStore} and {@link GlideClusterClient.sortReadOnly|sortReadOnly} commands. + * + * See https://valkey.io/commands/sort/ for more details. + */ +export type SortClusterOptions = SortBaseOptions; + +/** + * The `LIMIT` argument is commonly used to specify a subset of results from the + * matching elements, similar to the `LIMIT` clause in SQL (e.g., `SELECT LIMIT offset, count`). + */ +export type Limit = { + /** The starting position of the range, zero based. */ + offset: number; + /** The maximum number of elements to include in the range. A negative count returns all elements from the offset. */ + count: number; +}; + +/** @internal */ +export function createSort( + key: GlideString, + options?: SortOptions, + destination?: GlideString, +): command_request.Command { + return createSortImpl(RequestType.Sort, key, options, destination); } -function addStreamsArgs(keys_and_ids: Record, args: string[]) { - args.push("STREAMS"); +/** @internal */ +export function createSortReadOnly( + key: GlideString, + options?: SortOptions, +): command_request.Command { + return createSortImpl(RequestType.SortReadOnly, key, options); +} + +/** @internal */ +function createSortImpl( + cmd: RequestType, + key: GlideString, + options?: SortOptions, + destination?: GlideString, +): command_request.Command { + const args = [key]; + + if (options) { + if (options.limit) { + args.push( + "LIMIT", + options.limit.offset.toString(), + options.limit.count.toString(), + ); + } + + if (options.orderBy) { + args.push(options.orderBy); + } + + if (options.isAlpha) { + args.push("ALPHA"); + } - const pairs = Object.entries(keys_and_ids); + if (options.byPattern) { + args.push("BY", options.byPattern); + } - for (const [key] of pairs) { - args.push(key); + if (options.getPatterns) { + options.getPatterns.forEach((p) => args.push("GET", p)); + } } - for (const [, id] of pairs) { - args.push(id); + if (destination) args.push("STORE", destination); + + return createCommand(cmd, args); +} + +/** + * @internal + */ +export function createHStrlen( + key: GlideString, + field: GlideString, +): command_request.Command { + return createCommand(RequestType.HStrlen, [key, field]); +} + +/** @internal */ +export function createHRandField( + key: GlideString, + count?: number, + withValues?: boolean, +): command_request.Command { + const args = [key]; + if (count !== undefined) args.push(count.toString()); + if (withValues) args.push("WITHVALUES"); + return createCommand(RequestType.HRandField, args); +} + +/** + * @internal + */ +export function createHScan( + key: string, + cursor: string, + options?: BaseScanOptions, +): command_request.Command { + let args: string[] = [key, cursor]; + + if (options) { + args = args.concat(convertBaseScanOptionsToArgsArray(options)); } + + return createCommand(RequestType.HScan, args); } /** * @internal */ -export function createXRead( - keys_and_ids: Record, - options?: StreamReadOptions, +export function createZRandMember( + key: string, + count?: number, + withscores?: boolean, ): command_request.Command { - const args: string[] = []; + const args = [key]; + + if (count !== undefined) { + args.push(count.toString()); + } + + if (withscores) { + args.push("WITHSCORES"); + } + + return createCommand(RequestType.ZRandMember, args); +} + +/** @internal */ +export function createLastSave(): command_request.Command { + return createCommand(RequestType.LastSave, []); +} + +/** @internal */ +export function createLCS( + key1: GlideString, + key2: GlideString, + options?: { + len?: boolean; + idx?: { withMatchLen?: boolean; minMatchLen?: number }; + }, +): command_request.Command { + const args = [key1, key2]; if (options) { - addReadOptions(options, args); + if (options.len) args.push("LEN"); + else if (options.idx) { + args.push("IDX"); + if (options.idx.withMatchLen) args.push("WITHMATCHLEN"); + if (options.idx.minMatchLen !== undefined) + args.push("MINMATCHLEN", options.idx.minMatchLen.toString()); + } } - addStreamsArgs(keys_and_ids, args); + return createCommand(RequestType.LCS, args); +} - return createCommand(RequestType.XRead, args); +/** + * @internal + */ +export function createTouch(keys: GlideString[]): command_request.Command { + return createCommand(RequestType.Touch, keys); +} + +/** @internal */ +export function createRandomKey(): command_request.Command { + return createCommand(RequestType.RandomKey, []); +} + +/** @internal */ +export function createWatch(keys: GlideString[]): command_request.Command { + return createCommand(RequestType.Watch, keys); +} + +/** @internal */ +export function createUnWatch(): command_request.Command { + return createCommand(RequestType.UnWatch, []); +} + +/** @internal */ +export function createWait( + numreplicas: number, + timeout: number, +): command_request.Command { + return createCommand(RequestType.Wait, [ + numreplicas.toString(), + timeout.toString(), + ]); } +/** + * This base class represents the common set of optional arguments for the SCAN family of commands. + * Concrete implementations of this class are tied to specific SCAN commands (SCAN, HSCAN, SSCAN, + * and ZSCAN). + */ +export type BaseScanOptions = { + /** + * The match filter is applied to the result of the command and will only include + * strings that match the pattern specified. If the sorted set is large enough for scan commands to return + * only a subset of the sorted set then there could be a case where the result is empty although there are + * items that match the pattern specified. This is due to the default `COUNT` being `10` which indicates + * that it will only fetch and match `10` items from the list. + */ + readonly match?: string; + /** + * `COUNT` is a just a hint for the command for how many elements to fetch from the + * sorted set. `COUNT` could be ignored until the sorted set is large enough for the `SCAN` commands to + * represent the results as compact single-allocation packed encoding. + */ + readonly count?: number; +}; + /** * @internal */ -export function createXLen(key: string): command_request.Command { - return createCommand(RequestType.XLen, [key]); +function convertBaseScanOptionsToArgsArray(options: BaseScanOptions): string[] { + const args: string[] = []; + + if (options.match) { + args.push("MATCH", options.match); + } + + if (options.count !== undefined) { + args.push("COUNT", options.count.toString()); + } + + return args; } /** * @internal */ -export function createRename( +export function createZScan( key: string, - newKey: string, + cursor: string, + options?: BaseScanOptions, ): command_request.Command { - return createCommand(RequestType.Rename, [key, newKey]); + let args: string[] = [key, cursor]; + + if (options) { + args = args.concat(convertBaseScanOptionsToArgsArray(options)); + } + + return createCommand(RequestType.ZScan, args); +} + +/** @internal */ +export function createSetRange( + key: GlideString, + offset: number, + value: GlideString, +): command_request.Command { + return createCommand(RequestType.SetRange, [key, offset.toString(), value]); +} + +/** @internal */ +export function createAppend( + key: GlideString, + value: GlideString, +): command_request.Command { + return createCommand(RequestType.Append, [key, value]); } /** * @internal */ -export function createRenameNX( - key: string, - newKey: string, +export function createLMPop( + keys: GlideString[], + direction: ListDirection, + count?: number, ): command_request.Command { - return createCommand(RequestType.RenameNX, [key, newKey]); + const args: GlideString[] = [keys.length.toString(), ...keys, direction]; + + if (count !== undefined) { + args.push("COUNT"); + args.push(count.toString()); + } + + return createCommand(RequestType.LMPop, args); } /** * @internal */ -export function createPfAdd( - key: string, - elements: string[], +export function createBLMPop( + keys: GlideString[], + direction: ListDirection, + timeout: number, + count?: number, ): command_request.Command { - const args = [key, ...elements]; - return createCommand(RequestType.PfAdd, args); + const args: GlideString[] = [ + timeout.toString(), + keys.length.toString(), + ...keys, + direction, + ]; + + if (count !== undefined) { + args.push("COUNT"); + args.push(count.toString()); + } + + return createCommand(RequestType.BLMPop, args); } /** * @internal */ -export function createPfCount(keys: string[]): command_request.Command { - return createCommand(RequestType.PfCount, keys); +export function createPubSubChannels( + pattern?: GlideString, +): command_request.Command { + return createCommand(RequestType.PubSubChannels, pattern ? [pattern] : []); } /** * @internal */ -export function createObjectEncoding(key: string): command_request.Command { - return createCommand(RequestType.ObjectEncoding, [key]); +export function createPubSubNumPat(): command_request.Command { + return createCommand(RequestType.PubSubNumPat, []); } /** * @internal */ -export function createObjectFreq(key: string): command_request.Command { - return createCommand(RequestType.ObjectFreq, [key]); +export function createPubSubNumSub( + channels?: string[], +): command_request.Command { + return createCommand(RequestType.PubSubNumSub, channels ? channels : []); } /** * @internal */ -export function createObjectIdletime(key: string): command_request.Command { - return createCommand(RequestType.ObjectIdleTime, [key]); +export function createPubsubShardChannels( + pattern?: GlideString, +): command_request.Command { + return createCommand(RequestType.PubSubSChannels, pattern ? [pattern] : []); } /** * @internal */ -export function createObjectRefcount(key: string): command_request.Command { - return createCommand(RequestType.ObjectRefCount, [key]); +export function createPubSubShardNumSub( + channels?: string[], +): command_request.Command { + return createCommand(RequestType.PubSubSNumSub, channels ? channels : []); +} + +/** + * @internal + */ +export function createBZPopMax( + keys: GlideString[], + timeout: number, +): command_request.Command { + return createCommand(RequestType.BZPopMax, [...keys, timeout.toString()]); +} + +/** + * @internal + */ +export function createBZPopMin( + keys: GlideString[], + timeout: number, +): command_request.Command { + return createCommand(RequestType.BZPopMin, [...keys, timeout.toString()]); +} + +/** + * Time unit representation which is used in optional arguments for {@link BaseClient.getex|getex} and {@link BaseClient.set|set} command. + */ +export enum TimeUnit { + /** + * Set the specified expire time, in seconds. Equivalent to + * `EX` in the VALKEY API. + */ + Seconds = "EX", + /** + * Set the specified expire time, in milliseconds. Equivalent + * to `PX` in the VALKEY API. + */ + Milliseconds = "PX", + /** + * Set the specified Unix time at which the key will expire, + * in seconds. Equivalent to `EXAT` in the VALKEY API. + */ + UnixSeconds = "EXAT", + /** + * Set the specified Unix time at which the key will expire, + * in milliseconds. Equivalent to `PXAT` in the VALKEY API. + */ + UnixMilliseconds = "PXAT", +} + +/** + * @internal + */ +export function createGetEx( + key: GlideString, + options?: "persist" | { type: TimeUnit; duration: number }, +): command_request.Command { + const args = [key]; + + if (options) { + if (options !== "persist" && !Number.isInteger(options.duration)) { + throw new Error( + `Received expiry '${JSON.stringify( + options.duration, + )}'. Count must be an integer`, + ); + } + + if (options === "persist") { + args.push("PERSIST"); + } else { + args.push(options.type, options.duration.toString()); + } + } + + return createCommand(RequestType.GetEx, args); +} + +/** + * @internal + */ +export function createXAck( + key: GlideString, + group: GlideString, + ids: GlideString[], +): command_request.Command { + return createCommand(RequestType.XAck, [key, group, ...ids]); +} + +/** + * @internal + */ +export function createXGroupSetid( + key: GlideString, + groupName: GlideString, + id: GlideString, + entriesRead?: number, +): command_request.Command { + const args = [key, groupName, id]; + + if (entriesRead !== undefined) { + args.push("ENTRIESREAD"); + args.push(entriesRead.toString()); + } + + return createCommand(RequestType.XGroupSetId, args); } diff --git a/node/src/Errors.ts b/node/src/Errors.ts index d4a73f2958..8fa95fa6dd 100644 --- a/node/src/Errors.ts +++ b/node/src/Errors.ts @@ -32,3 +32,6 @@ export class ExecAbortError extends RequestError {} /// Errors that are thrown when a connection disconnects. These errors can be temporary, as the client will attempt to reconnect. export class ConnectionError extends RequestError {} + +/// Errors that are thrown when a request cannot be completed in current configuration settings. +export class ConfigurationError extends RequestError {} diff --git a/node/src/GlideClient.ts b/node/src/GlideClient.ts index da9ac34b84..3492fd715e 100644 --- a/node/src/GlideClient.ts +++ b/node/src/GlideClient.ts @@ -3,25 +3,99 @@ */ import * as net from "net"; -import { BaseClient, BaseClientConfiguration, ReturnType } from "./BaseClient"; import { + BaseClient, + BaseClientConfiguration, + Decoder, + DecoderOption, + GlideString, + PubSubMsg, + ReadFrom, // eslint-disable-line @typescript-eslint/no-unused-vars + ReturnType, +} from "./BaseClient"; +import { + FlushMode, + FunctionListOptions, + FunctionListResponse, + FunctionRestorePolicy, + FunctionStatsFullResponse, InfoOptions, + LolwutOptions, + SortOptions, createClientGetName, createClientId, createConfigGet, createConfigResetStat, createConfigRewrite, createConfigSet, + createCopy, createCustomCommand, + createDBSize, createEcho, + createFlushAll, + createFlushDB, + createFunctionDelete, + createFunctionDump, + createFunctionFlush, + createFunctionKill, + createFunctionList, + createFunctionLoad, + createFunctionRestore, + createFunctionStats, createInfo, + createLastSave, + createLolwut, + createMove, createPing, + createPublish, + createRandomKey, createSelect, + createSort, + createSortReadOnly, createTime, + createUnWatch, } from "./Commands"; import { connection_request } from "./ProtobufMessage"; import { Transaction } from "./Transaction"; +/* eslint-disable-next-line @typescript-eslint/no-namespace */ +export namespace GlideClientConfiguration { + /** + * Enum representing pubsub subscription modes. + * @see {@link https://valkey.io/docs/topics/pubsub/|Valkey PubSub Documentation} for more details. + */ + export enum PubSubChannelModes { + /** + * Use exact channel names. + */ + Exact = 0, + + /** + * Use channel name patterns. + */ + Pattern = 1, + } + + export type PubSubSubscriptions = { + /** + * Channels and patterns by modes. + */ + channelsAndPatterns: Partial>>; + + /** + * Optional callback to accept the incoming messages. + */ + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + callback?: (msg: PubSubMsg, context: any) => void; + + /** + * Arbitrary context to pass to the callback. + */ + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + context?: any; + }; +} + export type GlideClientConfiguration = BaseClientConfiguration & { /** * index of the logical database to connect to. @@ -52,12 +126,17 @@ export type GlideClientConfiguration = BaseClientConfiguration & { */ exponentBase: number; }; + /** + * PubSub subscriptions to be used for the client. + * Will be applied via SUBSCRIBE/PSUBSCRIBE commands during connection establishment. + */ + pubsubSubscriptions?: GlideClientConfiguration.PubSubSubscriptions; }; /** * Client used for connection to standalone Redis servers. - * For full documentation, see - * https://github.com/valkey-io/valkey-glide/wiki/NodeJS-wrapper#standalone + * + * @see For full documentation refer to {@link https://github.com/valkey-io/valkey-glide/wiki/NodeJS-wrapper#standalone|Valkey Glide Wiki}. */ export class GlideClient extends BaseClient { /** @@ -69,15 +148,17 @@ export class GlideClient extends BaseClient { const configuration = super.createClientRequest(options); configuration.databaseId = options.databaseId; configuration.connectionRetryStrategy = options.connectionBackoff; + this.configurePubsub(options, configuration); return configuration; } - public static createClient( + public static async createClient( options: GlideClientConfiguration, ): Promise { return super.createClientInternal( options, - (socket: net.Socket) => new GlideClient(socket), + (socket: net.Socket, options?: GlideClientConfiguration) => + new GlideClient(socket, options), ); } @@ -92,31 +173,40 @@ export class GlideClient extends BaseClient { ); } - /** Execute a transaction by processing the queued commands. - * See https://redis.io/topics/Transactions/ for details on Redis Transactions. + /** + * Execute a transaction by processing the queued commands. * - * @param transaction - A Transaction object containing a list of commands to be executed. + * @see {@link https://github.com/valkey-io/valkey-glide/wiki/NodeJS-wrapper#transaction|Valkey Glide Wiki} for details on Valkey Transactions. + * + * @param transaction - A {@link Transaction} object containing a list of commands to be executed. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * @returns A list of results corresponding to the execution of each command in the transaction. - * If a command returns a value, it will be included in the list. If a command doesn't return a value, - * the list entry will be null. - * If the transaction failed due to a WATCH command, `exec` will return `null`. + * If a command returns a value, it will be included in the list. If a command doesn't return a value, + * the list entry will be `null`. + * If the transaction failed due to a `WATCH` command, `exec` will return `null`. */ - public exec(transaction: Transaction): Promise { + public async exec( + transaction: Transaction, + decoder?: Decoder, + ): Promise { return this.createWritePromise( transaction.commands, - ).then((result: ReturnType[] | null) => { - return this.processResultWithSetCommands( + { decoder: decoder }, + ).then((result) => + this.processResultWithSetCommands( result, transaction.setCommandsIndexes, - ); - }); + ), + ); } /** Executes a single command, without checking inputs. Every part of the command, including subcommands, * should be added as a separate value in args. * - * See the [Glide for Redis Wiki](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command) - * for details on the restrictions and limitations of the custom command API. + * Note: An error will occur if the string decoder is used with commands that return only bytes as a response. + * + * @see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command|Valkey Glide Wiki} for details on the restrictions and limitations of the custom command API. * * @example * ```typescript @@ -125,17 +215,26 @@ export class GlideClient extends BaseClient { * console.log(result); // Output: Returns a list of all pub/sub clients * ``` */ - public customCommand(args: string[]): Promise { - return this.createWritePromise(createCustomCommand(args)); + public async customCommand( + args: GlideString[], + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createCustomCommand(args), { + decoder: decoder, + }); } - /** Ping the Redis server. - * See https://valkey.io/commands/ping/ for details. + /** + * Pings the server. + * + * @see {@link https://valkey.io/commands/ping/|valkey.io} for details. * - * @param message - An optional message to include in the PING command. - * If not provided, the server will respond with "PONG". - * If provided, the server will respond with a copy of the message. - * @returns - "PONG" if `message` is not provided, otherwise return a copy of `message`. + * @param options - (Optional) Additional parameters: + * - (Optional) `message` : a message to include in the `PING` command. + * + If not provided, the server will respond with `"PONG"`. + * + If provided, the server will respond with a copy of the message. + * - (Optional) `decoder`: see {@link DecoderOption}. + * @returns `"PONG"` if `message` is not provided, otherwise return a copy of `message`. * * @example * ```typescript @@ -151,26 +250,38 @@ export class GlideClient extends BaseClient { * console.log(result); // Output: 'Hello' * ``` */ - public ping(message?: string): Promise { - return this.createWritePromise(createPing(message)); + public async ping( + options?: { + message?: GlideString; + } & DecoderOption, + ): Promise { + return this.createWritePromise(createPing(options?.message), { + decoder: options?.decoder, + }); } - /** Get information and statistics about the Redis server. - * See https://valkey.io/commands/info/ for details. + /** + * Gets information and statistics about the server. + * + * @see {@link https://valkey.io/commands/info/|valkey.io} for details. * - * @param options - A list of InfoSection values specifying which sections of information to retrieve. - * When no parameter is provided, the default option is assumed. - * @returns a string containing the information for the sections requested. + * @param sections - (Optional) A list of {@link InfoOptions} values specifying which sections of information to retrieve. + * When no parameter is provided, {@link InfoOptions.Default|Default} is assumed. + * @returns A string containing the information for the sections requested. */ - public info(options?: InfoOptions[]): Promise { - return this.createWritePromise(createInfo(options)); + public async info(sections?: InfoOptions[]): Promise { + return this.createWritePromise(createInfo(sections), { + decoder: Decoder.String, + }); } - /** Change the currently selected Redis database. - * See https://valkey.io/commands/select/ for details. + /** + * Changes the currently selected database. + * + * @see {@link https://valkey.io/commands/select/|valkey.io} for details. * * @param index - The index of the database to select. - * @returns A simple OK response. + * @returns A simple `"OK"` response. * * @example * ```typescript @@ -179,14 +290,20 @@ export class GlideClient extends BaseClient { * console.log(result); // Output: 'OK' * ``` */ - public select(index: number): Promise<"OK"> { - return this.createWritePromise(createSelect(index)); + public async select(index: number): Promise<"OK"> { + return this.createWritePromise(createSelect(index), { + decoder: Decoder.String, + }); } - /** Get the name of the primary's connection. - * See https://valkey.io/commands/client-getname/ for more details. + /** + * Gets the name of the primary's connection. + * + * @see {@link https://valkey.io/commands/client-getname/|valkey.io} for more details. * - * @returns the name of the client connection as a string if a name is set, or null if no name is assigned. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. + * @returns The name of the client connection as a string if a name is set, or `null` if no name is assigned. * * @example * ```typescript @@ -195,12 +312,16 @@ export class GlideClient extends BaseClient { * console.log(result); // Output: 'Client Name' * ``` */ - public clientGetName(): Promise { - return this.createWritePromise(createClientGetName()); + public async clientGetName(decoder?: Decoder): Promise { + return this.createWritePromise(createClientGetName(), { + decoder: decoder, + }); } - /** Rewrite the configuration file with the current configuration. - * See https://valkey.io/commands/config-rewrite/ for details. + /** + * Rewrites the configuration file with the current configuration. + * + * @see {@link https://valkey.io/commands/config-rewrite/|valkey.io} for details. * * @returns "OK" when the configuration was rewritten properly. Otherwise, an error is thrown. * @@ -211,12 +332,16 @@ export class GlideClient extends BaseClient { * console.log(result); // Output: 'OK' * ``` */ - public configRewrite(): Promise<"OK"> { - return this.createWritePromise(createConfigRewrite()); + public async configRewrite(): Promise<"OK"> { + return this.createWritePromise(createConfigRewrite(), { + decoder: Decoder.String, + }); } - /** Resets the statistics reported by Redis using the INFO and LATENCY HISTOGRAM commands. - * See https://valkey.io/commands/config-resetstat/ for details. + /** + * Resets the statistics reported by the server using the `INFO` and `LATENCY HISTOGRAM` commands. + * + * @see {@link https://valkey.io/commands/config-resetstat/|valkey.io} for details. * * @returns always "OK". * @@ -227,23 +352,37 @@ export class GlideClient extends BaseClient { * console.log(result); // Output: 'OK' * ``` */ - public configResetStat(): Promise<"OK"> { - return this.createWritePromise(createConfigResetStat()); + public async configResetStat(): Promise<"OK"> { + return this.createWritePromise(createConfigResetStat(), { + decoder: Decoder.String, + }); } - /** Returns the current connection id. - * See https://valkey.io/commands/client-id/ for details. + /** + * Returns the current connection ID. * - * @returns the id of the client. + * @see {@link https://valkey.io/commands/client-id/|valkey.io} for details. + * + * @returns The ID of the connection. + * + * @example + * ```typescript + * const result = await client.clientId(); + * console.log("Connection id: " + result); + * ``` */ - public clientId(): Promise { + public async clientId(): Promise { return this.createWritePromise(createClientId()); } - /** Reads the configuration parameters of a running Redis server. - * See https://valkey.io/commands/config-get/ for details. + /** + * Reads the configuration parameters of the running server. + * + * @see {@link https://valkey.io/commands/config-get/|valkey.io} for details. * * @param parameters - A list of configuration parameter names to retrieve values for. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * * @returns A map of values corresponding to the configuration parameters. * @@ -254,16 +393,21 @@ export class GlideClient extends BaseClient { * console.log(result); // Output: {'timeout': '1000', 'maxmemory': '1GB'} * ``` */ - public configGet(parameters: string[]): Promise> { - return this.createWritePromise(createConfigGet(parameters)); + public async configGet( + parameters: string[], + decoder?: Decoder, + ): Promise> { + return this.createWritePromise(createConfigGet(parameters), { + decoder: decoder, + }); } - /** Set configuration parameters to the specified values. - * See https://valkey.io/commands/config-set/ for details. - * - * @param parameters - A List of keyValuePairs consisting of configuration parameters and their respective values to set. + /** + * Sets configuration parameters to the specified values. * - * @returns "OK" when the configuration was set properly. Otherwise an error is thrown. + * @see {@link https://valkey.io/commands/config-set/|valkey.io} for details. + * @param parameters - A map consisting of configuration parameters and their respective values to set. + * @returns `"OK"` when the configuration was set properly. Otherwise an error is thrown. * * @example * ```typescript @@ -272,14 +416,22 @@ export class GlideClient extends BaseClient { * console.log(result); // Output: 'OK' * ``` */ - public configSet(parameters: Record): Promise<"OK"> { - return this.createWritePromise(createConfigSet(parameters)); + public async configSet( + parameters: Record, + ): Promise<"OK"> { + return this.createWritePromise(createConfigSet(parameters), { + decoder: Decoder.String, + }); } - /** Echoes the provided `message` back. - * See https://valkey.io/commands/echo for more details. + /** + * Echoes the provided `message` back. + * + * @see {@link https://valkey.io/commands/echo|valkey.io} for more details. * * @param message - The message to be echoed back. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * @returns The provided `message`. * * @example @@ -289,25 +441,580 @@ export class GlideClient extends BaseClient { * console.log(echoedMessage); // Output: 'valkey-glide' * ``` */ - public echo(message: string): Promise { - return this.createWritePromise(createEcho(message)); + public async echo( + message: GlideString, + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createEcho(message), { + decoder, + }); + } + + /** + * Returns the server time. + * + * @see {@link https://valkey.io/commands/time/|valkey.io} for details. + * + * @returns The current server time as an `array` with two items: + * - A Unix timestamp, + * - The amount of microseconds already elapsed in the current second. + * + * @example + * ```typescript + * console.log(await client.time()); // Output: ['1710925775', '913580'] + * ``` + */ + public async time(): Promise<[string, string]> { + return this.createWritePromise(createTime(), { + decoder: Decoder.String, + }); + } + + /** + * Copies the value stored at the `source` to the `destination` key. If `destinationDB` is specified, + * the value will be copied to the database specified, otherwise the current database will be used. + * When `replace` is true, removes the `destination` key first if it already exists, otherwise performs + * no action. + * + * @see {@link https://valkey.io/commands/copy/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param source - The key to the source value. + * @param destination - The key where the value should be copied to. + * @param destinationDB - (Optional) The alternative logical database index for the destination key. + * If not provided, the current database will be used. + * @param replace - (Optional) If `true`, the `destination` key should be removed before copying the + * value to it. If not provided, no action will be performed if the key already exists. + * @returns `true` if `source` was copied, `false` if the `source` was not copied. + * + * @example + * ```typescript + * const result = await client.copy("set1", "set2"); + * console.log(result); // Output: true - "set1" was copied to "set2". + * ``` + * ```typescript + * const result = await client.copy("set1", "set2", { replace: true }); + * console.log(result); // Output: true - "set1" was copied to "set2". + * ``` + * ```typescript + * const result = await client.copy("set1", "set2", { destinationDB: 1, replace: false }); + * console.log(result); // Output: true - "set1" was copied to "set2". + * ``` + */ + public async copy( + source: GlideString, + destination: GlideString, + options?: { destinationDB?: number; replace?: boolean }, + ): Promise { + return this.createWritePromise( + createCopy(source, destination, options), + ); + } + + /** + * Move `key` from the currently selected database to the database specified by `dbIndex`. + * + * @see {@link https://valkey.io/commands/move/|valkey.io} for more details. + * + * @param key - The key to move. + * @param dbIndex - The index of the database to move `key` to. + * @returns `true` if `key` was moved, or `false` if the `key` already exists in the destination + * database or does not exist in the source database. + * + * @example + * ```typescript + * const result = await client.move("key", 1); + * console.log(result); // Output: true + * ``` + */ + public async move(key: GlideString, dbIndex: number): Promise { + return this.createWritePromise(createMove(key, dbIndex)); + } + + /** + * Displays a piece of generative computer art and the server version. + * + * @see {@link https://valkey.io/commands/lolwut/|valkey.io} for more details. + * + * @param options - (Optional) The LOLWUT options - see {@link LolwutOptions}. + * @returns A piece of generative computer art along with the current server version. + * + * @example + * ```typescript + * const response = await client.lolwut({ version: 6, parameters: [40, 20] }); + * console.log(response); // Output: "Redis ver. 7.2.3" - Indicates the current server version. + * ``` + */ + public async lolwut(options?: LolwutOptions): Promise { + return this.createWritePromise(createLolwut(options), { + decoder: Decoder.String, + }); + } + + /** + * Deletes a library and all its functions. + * + * @see {@link https://valkey.io/commands/function-delete/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param libraryCode - The library name to delete. + * @returns A simple `"OK"` response. + * + * @example + * ```typescript + * const result = await client.functionDelete("libName"); + * console.log(result); // Output: 'OK' + * ``` + */ + public async functionDelete(libraryCode: GlideString): Promise<"OK"> { + return this.createWritePromise(createFunctionDelete(libraryCode), { + decoder: Decoder.String, + }); + } + + /** + * Loads a library to Valkey. + * + * @see {@link https://valkey.io/commands/function-load/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param libraryCode - The source code that implements the library. + * @param options - (Optional) Additional parameters: + * - (Optional) `replace`: Whether the given library should overwrite a library with the same name if it + * already exists. + * - (Optional) `decoder`: see {@link DecoderOption}. + * @returns The library name that was loaded. + * + * @example + * ```typescript + * const code = "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return args[1] end)"; + * const result = await client.functionLoad(code, true); + * console.log(result); // Output: 'mylib' + * ``` + */ + public async functionLoad( + libraryCode: GlideString, + options?: { replace?: boolean } & DecoderOption, + ): Promise { + return this.createWritePromise( + createFunctionLoad(libraryCode, options?.replace), + { decoder: options?.decoder }, + ); + } + + /** + * Deletes all function libraries. + * + * @see {@link https://valkey.io/commands/function-flush/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param mode - (Optional) The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}. + * @returns A simple `"OK"` response. + * + * @example + * ```typescript + * const result = await client.functionFlush(FlushMode.SYNC); + * console.log(result); // Output: 'OK' + * ``` + */ + public async functionFlush(mode?: FlushMode): Promise<"OK"> { + return this.createWritePromise(createFunctionFlush(mode), { + decoder: Decoder.String, + }); } - /** Returns the server time - * See https://valkey.io/commands/time/ for details. + /** + * Returns information about the functions and libraries. + * + * @see {@link https://valkey.io/commands/function-list/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. * - * @returns - The current server time as a two items `array`: - * A Unix timestamp and the amount of microseconds already elapsed in the current second. - * The returned `array` is in a [Unix timestamp, Microseconds already elapsed] format. + * @param options - (Optional) See {@link FunctionListOptions} and {@link DecoderOption}. + * @returns Info about all or selected libraries and their functions in {@link FunctionListResponse} format. * * @example * ```typescript - * // Example usage of time method without any argument - * const result = await client.time(); - * console.log(result); // Output: ['1710925775', '913580'] + * // Request info for specific library including the source code + * const result1 = await client.functionList({ libNamePattern: "myLib*", withCode: true }); + * // Request info for all libraries + * const result2 = await client.functionList(); + * console.log(result2); // Output: + * // [{ + * // "library_name": "myLib5_backup", + * // "engine": "LUA", + * // "functions": [{ + * // "name": "myfunc", + * // "description": null, + * // "flags": [ "no-writes" ], + * // }], + * // "library_code": "#!lua name=myLib5_backup \n redis.register_function('myfunc', function(keys, args) return args[1] end)" + * // }] * ``` */ - public time(): Promise<[string, string]> { - return this.createWritePromise(createTime()); + public async functionList( + options?: FunctionListOptions & DecoderOption, + ): Promise { + return this.createWritePromise(createFunctionList(options), { + decoder: options?.decoder, + }); + } + + /** + * Returns information about the function that's currently running and information about the + * available execution engines. + * + * FUNCTION STATS runs on all nodes of the server, including primary and replicas. + * The response includes a mapping from node address to the command response for that node. + * + * @see {@link https://valkey.io/commands/function-stats/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. + * @returns A Record where the key is the node address and the value is a Record with two keys: + * - `"running_script"`: Information about the running script, or `null` if no script is running. + * - `"engines"`: Information about available engines and their stats. + * - see example for more details. + * @example + * ```typescript + * const response = await client.functionStats(); + * console.log(response); // Example output: + * // { + * // "127.0.0.1:6379": { // Response from the primary node + * // "running_script": { + * // "name": "foo", + * // "command": ["FCALL", "foo", "0", "hello"], + * // "duration_ms": 7758 + * // }, + * // "engines": { + * // "LUA": { + * // "libraries_count": 1, + * // "functions_count": 1 + * // } + * // } + * // }, + * // "127.0.0.1:6380": { // Response from a replica node + * // "running_script": null, + * // "engines": { + * // "LUA": { + * // "libraries_count": 1, + * // "functions_count": 1 + * // } + * // } + * // } + * // } + * ``` + */ + public async functionStats( + decoder?: Decoder, + ): Promise { + return this.createWritePromise(createFunctionStats(), { + decoder, + }); + } + + /** + * Kills a function that is currently executing. + * `FUNCTION KILL` terminates read-only functions only. + * `FUNCTION KILL` runs on all nodes of the server, including primary and replicas. + * + * @see {@link https://valkey.io/commands/function-kill/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @returns `"OK"` if function is terminated. Otherwise, throws an error. + * @example + * ```typescript + * await client.functionKill(); + * ``` + */ + public async functionKill(): Promise<"OK"> { + return this.createWritePromise(createFunctionKill(), { + decoder: Decoder.String, + }); + } + + /** + * Returns the serialized payload of all loaded libraries. + * + * @see {@link https://valkey.io/commands/function-dump/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @returns The serialized payload of all loaded libraries. + * + * @example + * ```typescript + * const data = await client.functionDump(); + * // data can be used to restore loaded functions on any Valkey instance + * ``` + */ + public async functionDump(): Promise { + return this.createWritePromise(createFunctionDump(), { + decoder: Decoder.Bytes, + }); + } + + /** + * Restores libraries from the serialized payload returned by {@link functionDump}. + * + * @see {@link https://valkey.io/commands/function-restore/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param payload - The serialized data from {@link functionDump}. + * @param policy - (Optional) A policy for handling existing libraries, see {@link FunctionRestorePolicy}. + * {@link FunctionRestorePolicy.APPEND} is used by default. + * @returns `"OK"`. + * + * @example + * ```typescript + * await client.functionRestore(data, FunctionRestorePolicy.FLUSH); + * ``` + */ + public async functionRestore( + payload: Buffer, + policy?: FunctionRestorePolicy, + ): Promise<"OK"> { + return this.createWritePromise(createFunctionRestore(payload, policy), { + decoder: Decoder.String, + }); + } + + /** + * Deletes all the keys of all the existing databases. This command never fails. + * + * @see {@link https://valkey.io/commands/flushall/|valkey.io} for more details. + * + * @param mode - (Optional) The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}. + * @returns `"OK"`. + * + * @example + * ```typescript + * const result = await client.flushall(FlushMode.SYNC); + * console.log(result); // Output: 'OK' + * ``` + */ + public async flushall(mode?: FlushMode): Promise<"OK"> { + return this.createWritePromise(createFlushAll(mode), { + decoder: Decoder.String, + }); + } + + /** + * Deletes all the keys of the currently selected database. This command never fails. + * + * @see {@link https://valkey.io/commands/flushdb/|valkey.io} for more details. + * + * @param mode - (Optional) The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}. + * @returns `"OK"`. + * + * @example + * ```typescript + * const result = await client.flushdb(FlushMode.SYNC); + * console.log(result); // Output: 'OK' + * ``` + */ + public async flushdb(mode?: FlushMode): Promise<"OK"> { + return this.createWritePromise(createFlushDB(mode), { + decoder: Decoder.String, + }); + } + + /** + * Returns the number of keys in the currently selected database. + * + * @see {@link https://valkey.io/commands/dbsize/|valkey.io} for more details. + * + * @returns The number of keys in the currently selected database. + * + * @example + * ```typescript + * const numKeys = await client.dbsize(); + * console.log("Number of keys in the current database: ", numKeys); + * ``` + */ + public async dbsize(): Promise { + return this.createWritePromise(createDBSize()); + } + + /** Publish a message on pubsub channel. + * + * @see {@link https://valkey.io/commands/publish/|valkey.io} for more details. + * + * @param message - Message to publish. + * @param channel - Channel to publish the message on. + * @returns - Number of subscriptions in primary node that received the message. + * Note that this value does not include subscriptions that configured on replicas. + * + * @example + * ```typescript + * // Example usage of publish command + * const result = await client.publish("Hi all!", "global-channel"); + * console.log(result); // Output: 1 - This message was posted to 1 subscription which is configured on primary node + * ``` + */ + public async publish( + message: GlideString, + channel: GlideString, + ): Promise { + return this.createWritePromise(createPublish(message, channel)); + } + + /** + * Sorts the elements in the list, set, or sorted set at `key` and returns the result. + * + * The `sort` command can be used to sort elements based on different criteria and + * apply transformations on sorted elements. + * + * To store the result into a new key, see {@link sortStore}. + * + * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details. + * + * @param key - The key of the list, set, or sorted set to be sorted. + * @param options - (Optional) The {@link SortOptions} and {@link DecoderOption}. + * + * @returns An `Array` of sorted elements. + * + * @example + * ```typescript + * await client.hset("user:1", new Map([["name", "Alice"], ["age", "30"]])); + * await client.hset("user:2", new Map([["name", "Bob"], ["age", "25"]])); + * await client.lpush("user_ids", ["2", "1"]); + * const result = await client.sort("user_ids", { byPattern: "user:*->age", getPattern: ["user:*->name"] }); + * console.log(result); // Output: [ 'Bob', 'Alice' ] - Returns a list of the names sorted by age + * ``` + */ + public async sort( + key: GlideString, + options?: SortOptions & DecoderOption, + ): Promise<(GlideString | null)[]> { + return this.createWritePromise(createSort(key, options), { + decoder: options?.decoder, + }); + } + + /** + * Sorts the elements in the list, set, or sorted set at `key` and returns the result. + * + * The `sortReadOnly` command can be used to sort elements based on different criteria and + * apply transformations on sorted elements. + * + * This command is routed depending on the client's {@link ReadFrom} strategy. + * + * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details. + * @remarks Since Valkey version 7.0.0. + * + * @param key - The key of the list, set, or sorted set to be sorted. + * @param options - (Optional) The {@link SortOptions} and {@link DecoderOption}. + * @returns An `Array` of sorted elements + * + * @example + * ```typescript + * await client.hset("user:1", new Map([["name", "Alice"], ["age", "30"]])); + * await client.hset("user:2", new Map([["name", "Bob"], ["age", "25"]])); + * await client.lpush("user_ids", ["2", "1"]); + * const result = await client.sortReadOnly("user_ids", { byPattern: "user:*->age", getPattern: ["user:*->name"] }); + * console.log(result); // Output: [ 'Bob', 'Alice' ] - Returns a list of the names sorted by age + * ``` + */ + public async sortReadOnly( + key: GlideString, + options?: SortOptions & DecoderOption, + ): Promise<(GlideString | null)[]> { + return this.createWritePromise(createSortReadOnly(key, options), { + decoder: options?.decoder, + }); + } + + /** + * Sorts the elements in the list, set, or sorted set at `key` and stores the result in + * `destination`. + * + * The `sort` command can be used to sort elements based on different criteria and + * apply transformations on sorted elements, and store the result in a new key. + * + * To get the sort result without storing it into a key, see {@link sort} or {@link sortReadOnly}. + * + * @see {@link https://valkey.io/commands/sort|valkey.io} for more details. + * @remarks When in cluster mode, `destination` and `key` must map to the same hash slot. + * + * @param key - The key of the list, set, or sorted set to be sorted. + * @param destination - The key where the sorted result will be stored. + * @param options - (Optional) The {@link SortOptions}. + * @returns The number of elements in the sorted key stored at `destination`. + * + * @example + * ```typescript + * await client.hset("user:1", new Map([["name", "Alice"], ["age", "30"]])); + * await client.hset("user:2", new Map([["name", "Bob"], ["age", "25"]])); + * await client.lpush("user_ids", ["2", "1"]); + * const sortedElements = await client.sortStore("user_ids", "sortedList", { byPattern: "user:*->age", getPattern: ["user:*->name"] }); + * console.log(sortedElements); // Output: 2 - number of elements sorted and stored + * console.log(await client.lrange("sortedList", 0, -1)); // Output: [ 'Bob', 'Alice' ] - Returns a list of the names sorted by age stored in `sortedList` + * ``` + */ + public async sortStore( + key: GlideString, + destination: GlideString, + options?: SortOptions, + ): Promise { + return this.createWritePromise(createSort(key, options, destination)); + } + + /** + * Returns `UNIX TIME` of the last DB save timestamp or startup timestamp if no save + * was made since then. + * + * @see {@link https://valkey.io/commands/lastsave/|valkey.io} for more details. + * + * @returns `UNIX TIME` of the last DB save executed with success. + * + * @example + * ```typescript + * const timestamp = await client.lastsave(); + * console.log("Last DB save was done at " + timestamp); + * ``` + */ + public async lastsave(): Promise { + return this.createWritePromise(createLastSave()); + } + + /** + * Returns a random existing key name from the currently selected database. + * + * @see {@link https://valkey.io/commands/randomkey/|valkey.io} for more details. + * + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. + * @returns A random existing key name from the currently selected database. + * + * @example + * ```typescript + * const result = await client.randomKey(); + * console.log(result); // Output: "key12" - "key12" is a random existing key name from the currently selected database. + * ``` + */ + public async randomKey(decoder?: Decoder): Promise { + return this.createWritePromise(createRandomKey(), { decoder: decoder }); + } + + /** + * Flushes all the previously watched keys for a transaction. Executing a transaction will + * automatically flush all previously watched keys. + * + * @see {@link https://valkey.io/commands/unwatch/|valkey.io} and {@link https://valkey.io/topics/transactions/#cas|Valkey Glide Wiki} for more details. + * + * @returns A simple `"OK"` response. + * + * @example + * ```typescript + * let response = await client.watch(["sampleKey"]); + * console.log(response); // Output: "OK" + * response = await client.unwatch(); + * console.log(response); // Output: "OK" + * ``` + */ + public async unwatch(): Promise<"OK"> { + return this.createWritePromise(createUnWatch(), { + decoder: Decoder.String, + }); } } diff --git a/node/src/GlideClusterClient.ts b/node/src/GlideClusterClient.ts index 0fda4cb2ac..56e75aad16 100644 --- a/node/src/GlideClusterClient.ts +++ b/node/src/GlideClusterClient.ts @@ -3,25 +3,73 @@ */ import * as net from "net"; -import { BaseClient, BaseClientConfiguration, ReturnType } from "./BaseClient"; import { + BaseClient, + BaseClientConfiguration, + Decoder, + DecoderOption, + GlideString, + PubSubMsg, + ReadFrom, // eslint-disable-line @typescript-eslint/no-unused-vars + ReturnType, +} from "./BaseClient"; +import { + FlushMode, + FunctionListOptions, + FunctionListResponse, + FunctionRestorePolicy, + FunctionStatsSingleResponse, InfoOptions, + LolwutOptions, + SortClusterOptions, createClientGetName, createClientId, createConfigGet, createConfigResetStat, createConfigRewrite, createConfigSet, + createCopy, createCustomCommand, + createDBSize, createEcho, + createFCall, + createFCallReadOnly, + createFlushAll, + createFlushDB, + createFunctionDelete, + createFunctionDump, + createFunctionFlush, + createFunctionKill, + createFunctionList, + createFunctionLoad, + createFunctionRestore, + createFunctionStats, createInfo, + createLastSave, + createLolwut, createPing, + createPubSubShardNumSub, + createPublish, + createPubsubShardChannels, + createRandomKey, + createSort, + createSortReadOnly, createTime, + createUnWatch, } from "./Commands"; import { RequestError } from "./Errors"; import { command_request, connection_request } from "./ProtobufMessage"; import { ClusterTransaction } from "./Transaction"; +/** An extension to command option types with {@link Routes}. */ +export type RouteOption = { + /** + * Specifies the routing configuration for the command. + * The client will route the command to the nodes defined by `route`. + */ + route?: Routes; +}; + /** * Represents a manually configured interval for periodic checks. */ @@ -48,7 +96,50 @@ export type PeriodicChecks = * Manually configured interval for periodic checks. */ | PeriodicChecksManualInterval; -export type ClusterClientConfiguration = BaseClientConfiguration & { + +/* eslint-disable-next-line @typescript-eslint/no-namespace */ +export namespace GlideClusterClientConfiguration { + /** + * Enum representing pubsub subscription modes. + * @see {@link https://valkey.io/docs/topics/pubsub/|Valkey PubSub Documentation} for more details. + */ + export enum PubSubChannelModes { + /** + * Use exact channel names. + */ + Exact = 0, + + /** + * Use channel name patterns. + */ + Pattern = 1, + + /** + * Use sharded pubsub. Available since Valkey version 7.0. + */ + Sharded = 2, + } + + export type PubSubSubscriptions = { + /** + * Channels and patterns by modes. + */ + channelsAndPatterns: Partial>>; + + /** + * Optional callback to accept the incoming messages. + */ + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + callback?: (msg: PubSubMsg, context: any) => void; + + /** + * Arbitrary context to pass to the callback. + */ + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + context?: any; + }; +} +export type GlideClusterClientConfiguration = BaseClientConfiguration & { /** * Configure the periodic topology checks. * These checks evaluate changes in the cluster's topology, triggering a slot refresh when detected. @@ -56,6 +147,12 @@ export type ClusterClientConfiguration = BaseClientConfiguration & { * If not set, `enabledDefaultConfigs` will be used. */ periodicChecks?: PeriodicChecks; + + /** + * PubSub subscriptions to be used for the client. + * Will be applied via SUBSCRIBE/PSUBSCRIBE/SSUBSCRIBE commands during connection establishment. + */ + pubsubSubscriptions?: GlideClusterClientConfiguration.PubSubSubscriptions; }; /** @@ -201,15 +298,15 @@ function toProtobufRoute( /** * Client used for connection to cluster Redis servers. - * For full documentation, see - * https://github.com/valkey-io/valkey-glide/wiki/NodeJS-wrapper#cluster + * + * @see For full documentation refer to {@link https://github.com/valkey-io/valkey-glide/wiki/NodeJS-wrapper#cluster|Valkey Glide Wiki}. */ export class GlideClusterClient extends BaseClient { /** * @internal */ protected createClientRequest( - options: ClusterClientConfiguration, + options: GlideClusterClientConfiguration, ): connection_request.IConnectionRequest { const configuration = super.createClientRequest(options); configuration.clusterModeEnabled = true; @@ -230,15 +327,16 @@ export class GlideClusterClient extends BaseClient { } } + this.configurePubsub(options, configuration); return configuration; } public static async createClient( - options: ClusterClientConfiguration, + options: GlideClusterClientConfiguration, ): Promise { return await super.createClientInternal( options, - (socket: net.Socket, options?: ClusterClientConfiguration) => + (socket: net.Socket, options?: GlideClusterClientConfiguration) => new GlideClusterClient(socket, options), ); } @@ -259,57 +357,79 @@ export class GlideClusterClient extends BaseClient { * The command will be routed automatically based on the passed command's default request policy, unless `route` is provided, * in which case the client will route the command to the nodes defined by `route`. * - * See the [Glide for Redis Wiki](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command) - * for details on the restrictions and limitations of the custom command API. + * Note: An error will occur if the string decoder is used with commands that return only bytes as a response. + * + * @see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command|Glide for Valkey Wiki} for details on the restrictions and limitations of the custom command API. * * @example * ```typescript * // Example usage of customCommand method to retrieve pub/sub clients with routing to all primary nodes - * const result = await client.customCommand(["CLIENT", "LIST", "TYPE", "PUBSUB"], "allPrimaries"); + * const result = await client.customCommand(["CLIENT", "LIST", "TYPE", "PUBSUB"], {route: "allPrimaries", decoder: Decoder.String}); * console.log(result); // Output: Returns a list of all pub/sub clients * ``` */ - public customCommand(args: string[], route?: Routes): Promise { + public async customCommand( + args: GlideString[], + options?: { route?: Routes; decoder?: Decoder }, + ): Promise> { const command = createCustomCommand(args); - return super.createWritePromise(command, toProtobufRoute(route)); + return super.createWritePromise(command, { + route: toProtobufRoute(options?.route), + decoder: options?.decoder, + }); } - /** Execute a transaction by processing the queued commands. - * See https://redis.io/topics/Transactions/ for details on Redis Transactions. + /** + * Execute a transaction by processing the queued commands. * - * @param transaction - A ClusterTransaction object containing a list of commands to be executed. - * @param route - If `route` is not provided, the transaction will be routed to the slot owner of the first key found in the transaction. - * If no key is found, the command will be sent to a random node. - * If `route` is provided, the client will route the command to the nodes defined by `route`. + * @see {@link https://github.com/valkey-io/valkey-glide/wiki/NodeJS-wrapper#transaction|Valkey Glide Wiki} for details on Valkey Transactions. + * + * @param transaction - A {@link ClusterTransaction} object containing a list of commands to be executed. + * @param route - (Optional) If `route` is not provided, the transaction will be routed to the slot owner of the first key found in the transaction. + * If no key is found, the command will be sent to a random node. + * If `route` is provided, the client will route the command to the nodes defined by `route`. + * @param decoder - (Optional) {@link Decoder} type which defines how to handle the response. + * If not set, the {@link BaseClientConfiguration.defaultDecoder|default decoder} will be used. * @returns A list of results corresponding to the execution of each command in the transaction. - * If a command returns a value, it will be included in the list. If a command doesn't return a value, - * the list entry will be null. - * If the transaction failed due to a WATCH command, `exec` will return `null`. + * If a command returns a value, it will be included in the list. If a command doesn't return a value, + * the list entry will be `null`. + * If the transaction failed due to a `WATCH` command, `exec` will return `null`. */ - public exec( + public async exec( transaction: ClusterTransaction, - route?: SingleNodeRoute, + options?: { + route?: SingleNodeRoute; + decoder?: Decoder; + }, ): Promise { return this.createWritePromise( transaction.commands, - toProtobufRoute(route), - ).then((result: ReturnType[] | null) => { - return this.processResultWithSetCommands( + { + route: toProtobufRoute(options?.route), + decoder: options?.decoder, + }, + ).then((result) => + this.processResultWithSetCommands( result, transaction.setCommandsIndexes, - ); - }); + ), + ); } - /** Ping the Redis server. - * See https://valkey.io/commands/ping/ for details. + /** + * Pings the server. + * + * The command will be routed to all primary nodes, unless `route` is provided. + * + * @see {@link https://valkey.io/commands/ping/|valkey.io} for details. * - * @param message - An optional message to include in the PING command. - * If not provided, the server will respond with "PONG". - * If provided, the server will respond with a copy of the message. - * @param route - The command will be routed to all primaries, unless `route` is provided, in which - * case the client will route the command to the nodes defined by `route`. - * @returns - "PONG" if `message` is not provided, otherwise return a copy of `message`. + * @param options - (Optional) Additional parameters: + * - (Optional) `message` : a message to include in the `PING` command. + * + If not provided, the server will respond with `"PONG"`. + * + If provided, the server will respond with a copy of the message. + * - (Optional) `route`: see {@link RouteOption}. + * - (Optional) `decoder`: see {@link DecoderOption}. + * @returns `"PONG"` if `message` is not provided, otherwise return a copy of `message`. * * @example * ```typescript @@ -325,42 +445,54 @@ export class GlideClusterClient extends BaseClient { * console.log(result); // Output: 'Hello' * ``` */ - public ping(message?: string, route?: Routes): Promise { - return this.createWritePromise( - createPing(message), - toProtobufRoute(route), - ); + public async ping( + options?: { + message?: GlideString; + } & RouteOption & + DecoderOption, + ): Promise { + return this.createWritePromise(createPing(options?.message), { + route: toProtobufRoute(options?.route), + decoder: options?.decoder, + }); } - /** Get information and statistics about the Redis server. - * See https://valkey.io/commands/info/ for details. + /** + * Gets information and statistics about the server. + * + * The command will be routed to all primary nodes, unless `route` is provided. + * + * @see {@link https://valkey.io/commands/info/|valkey.io} for details. * - * @param options - A list of InfoSection values specifying which sections of information to retrieve. - * When no parameter is provided, the default option is assumed. - * @param route - The command will be routed to all primaries, unless `route` is provided, in which - * case the client will route the command to the nodes defined by `route`. - * @returns a string containing the information for the sections requested. When specifying a route other than a single node, - * it returns a dictionary where each address is the key and its corresponding node response is the value. + * @param options - (Optional) Additional parameters: + * - (Optional) `sections`: a list of {@link InfoOptions} values specifying which sections of information to retrieve. + * When no parameter is provided, {@link InfoOptions.Default|Default} is assumed. + * - (Optional) `route`: see {@link RouteOption}. + * @returns A string containing the information for the sections requested. + * When specifying a route other than a single node, + * it returns a dictionary where each address is the key and its corresponding node response is the value. */ - public info( - options?: InfoOptions[], - route?: Routes, + public async info( + options?: { sections?: InfoOptions[] } & RouteOption, ): Promise> { return this.createWritePromise>( - createInfo(options), - toProtobufRoute(route), + createInfo(options?.sections), + { route: toProtobufRoute(options?.route), decoder: Decoder.String }, ); } - /** Get the name of the connection to which the request is routed. - * See https://valkey.io/commands/client-getname/ for more details. + /** + * Gets the name of the connection to which the request is routed. * - * @param route - The command will be routed a random node, unless `route` is provided, in which - * case the client will route the command to the nodes defined by `route`. + * The command will be routed to a random node, unless `route` is provided. * - * @returns - the name of the client connection as a string if a name is set, or null if no name is assigned. - * When specifying a route other than a single node, it returns a dictionary where each address is the key and - * its corresponding node response is the value. + * @see {@link https://valkey.io/commands/client-getname/|valkey.io} for details. + * + * @param options - (Optional) See {@link RouteOption} and {@link DecoderOption}. + * + * @returns - The name of the client connection as a string if a name is set, or `null` if no name is assigned. + * When specifying a route other than a single node, it returns a dictionary where each address is the key and + * its corresponding node response is the value. * * @example * ```typescript @@ -376,22 +508,28 @@ export class GlideClusterClient extends BaseClient { * console.log(result); // Output: {'addr': 'Connection Name', 'addr2': 'Connection Name', 'addr3': 'Connection Name'} * ``` */ - public clientGetName( - route?: Routes, - ): Promise> { - return this.createWritePromise>( + public async clientGetName( + options?: RouteOption & DecoderOption, + ): Promise> { + return this.createWritePromise>( createClientGetName(), - toProtobufRoute(route), + { + route: toProtobufRoute(options?.route), + decoder: options?.decoder, + }, ); } - /** Rewrite the configuration file with the current configuration. - * See https://valkey.io/commands/config-rewrite/ for details. + /** + * Rewrites the configuration file with the current configuration. + * + * The command will be routed to a all nodes, unless `route` is provided. * - * @param route - The command will be routed to all nodes, unless `route` is provided, in which - * case the client will route the command to the nodes defined by `route`. + * @see {@link https://valkey.io/commands/config-rewrite/|valkey.io} for details. * - * @returns "OK" when the configuration was rewritten properly. Otherwise, an error is thrown. + * @param route - (Optional) Specifies the routing configuration for the command. + * The client will route the command to the nodes defined by `route`. + * @returns `"OK"` when the configuration was rewritten properly. Otherwise, an error is thrown. * * @example * ```typescript @@ -400,20 +538,23 @@ export class GlideClusterClient extends BaseClient { * console.log(result); // Output: 'OK' * ``` */ - public configRewrite(route?: Routes): Promise<"OK"> { - return this.createWritePromise( - createConfigRewrite(), - toProtobufRoute(route), - ); + public async configRewrite(route?: Routes): Promise<"OK"> { + return this.createWritePromise(createConfigRewrite(), { + route: toProtobufRoute(route), + decoder: Decoder.String, + }); } - /** Resets the statistics reported by Redis using the INFO and LATENCY HISTOGRAM commands. - * See https://valkey.io/commands/config-resetstat/ for details. + /** + * Resets the statistics reported by the server using the `INFO` and `LATENCY HISTOGRAM` commands. + * + * The command will be routed to all nodes, unless `route` is provided. * - * @param route - The command will be routed to all nodes, unless `route` is provided, in which - * case the client will route the command to the nodes defined by `route`. + * @see {@link https://valkey.io/commands/config-resetstat/|valkey.io} for details. * - * @returns always "OK". + * @param route - (Optional) Specifies the routing configuration for the command. + * The client will route the command to the nodes defined by `route`. + * @returns always `"OK"`. * * @example * ```typescript @@ -422,38 +563,51 @@ export class GlideClusterClient extends BaseClient { * console.log(result); // Output: 'OK' * ``` */ - public configResetStat(route?: Routes): Promise<"OK"> { - return this.createWritePromise( - createConfigResetStat(), - toProtobufRoute(route), - ); + public async configResetStat(route?: Routes): Promise<"OK"> { + return this.createWritePromise(createConfigResetStat(), { + route: toProtobufRoute(route), + decoder: Decoder.String, + }); } - /** Returns the current connection id. - * See https://valkey.io/commands/client-id/ for details. + /** + * Returns the current connection ID. + * + * The command will be routed to a random node, unless `route` is provided. + * + * @see {@link https://valkey.io/commands/client-id/|valkey.io} for details. + * + * @param options - (Optional) See {@link RouteOption}. + * @returns The ID of the connection. When specifying a route other than a single node, + * it returns a dictionary where each address is the key and its corresponding node response is the value. * - * @param route - The command will be routed to a random node, unless `route` is provided, in which - * case the client will route the command to the nodes defined by `route`. - * @returns the id of the client. When specifying a route other than a single node, - * it returns a dictionary where each address is the key and its corresponding node response is the value. + * @example + * ```typescript + * const result = await client.clientId(); + * console.log("Connection id: " + result); + * ``` */ - public clientId(route?: Routes): Promise> { + public async clientId( + options?: RouteOption, + ): Promise> { return this.createWritePromise>( createClientId(), - toProtobufRoute(route), + { route: toProtobufRoute(options?.route) }, ); } - /** Reads the configuration parameters of a running Redis server. - * See https://valkey.io/commands/config-get/ for details. + /** + * Reads the configuration parameters of the running server. + * + * The command will be routed to a random node, unless `route` is provided. + * + * @see {@link https://valkey.io/commands/config-get/|valkey.io} for details. * * @param parameters - A list of configuration parameter names to retrieve values for. - * @param route - The command will be routed to a random node, unless `route` is provided, in which - * case the client will route the command to the nodes defined by `route`. - * If `route` is not provided, the command will be sent to a random node. + * @param options - (Optional) See {@link RouteOption} and {@link DecoderOption}. * * @returns A map of values corresponding to the configuration parameters. When specifying a route other than a single node, - * it returns a dictionary where each address is the key and its corresponding node response is the value. + * it returns a dictionary where each address is the key and its corresponding node response is the value. * * @example * ```typescript @@ -469,24 +623,25 @@ export class GlideClusterClient extends BaseClient { * console.log(result); // Output: {'timeout': '1000', 'maxmemory': '1GB'} * ``` */ - public configGet( + public async configGet( parameters: string[], - route?: Routes, - ): Promise>> { - return this.createWritePromise>>( - createConfigGet(parameters), - toProtobufRoute(route), - ); + options?: RouteOption & DecoderOption, + ): Promise>> { + return this.createWritePromise(createConfigGet(parameters), { + route: toProtobufRoute(options?.route), + decoder: options?.decoder, + }); } - /** Set configuration parameters to the specified values. - * See https://valkey.io/commands/config-set/ for details. + /** + * Sets configuration parameters to the specified values. + * + * The command will be routed to all nodes, unless `route` is provided. * - * @param parameters - A List of keyValuePairs consisting of configuration parameters and their respective values to set. - * @param route - The command will be routed to all nodes, unless `route` is provided, in which - * case the client will route the command to the nodes defined by `route`. - * If `route` is not provided, the command will be sent to the all nodes. + * @see {@link https://valkey.io/commands/config-set/|valkey.io} for details. * + * @param parameters - A map consisting of configuration parameters and their respective values to set. + * @param options - (Optional) See {@link RouteOption}. * @returns "OK" when the configuration was set properly. Otherwise an error is thrown. * * @example @@ -496,24 +651,27 @@ export class GlideClusterClient extends BaseClient { * console.log(result); // Output: 'OK' * ``` */ - public configSet( - parameters: Record, - route?: Routes, + public async configSet( + parameters: Record, + options?: RouteOption, ): Promise<"OK"> { - return this.createWritePromise( - createConfigSet(parameters), - toProtobufRoute(route), - ); + return this.createWritePromise(createConfigSet(parameters), { + route: toProtobufRoute(options?.route), + decoder: Decoder.String, + }); } - /** Echoes the provided `message` back. - * See https://valkey.io/commands/echo for more details. + /** + * Echoes the provided `message` back. + * + * The command will be routed to a random node, unless `route` is provided. + * + * @see {@link https://valkey.io/commands/echo/|valkey.io} for details. * * @param message - The message to be echoed back. - * @param route - The command will be routed to a random node, unless `route` is provided, in which - * case the client will route the command to the nodes defined by `route`. + * @param options - (Optional) See {@link RouteOption} and {@link DecoderOption}. * @returns The provided `message`. When specifying a route other than a single node, - * it returns a dictionary where each address is the key and its corresponding node response is the value. + * it returns a dictionary where each address is the key and its corresponding node response is the value. * * @example * ```typescript @@ -528,25 +686,29 @@ export class GlideClusterClient extends BaseClient { * console.log(echoedMessage); // Output: {'addr': 'valkey-glide', 'addr2': 'valkey-glide', 'addr3': 'valkey-glide'} * ``` */ - public echo( - message: string, - route?: Routes, - ): Promise> { - return this.createWritePromise( - createEcho(message), - toProtobufRoute(route), - ); + public async echo( + message: GlideString, + options?: RouteOption & DecoderOption, + ): Promise> { + return this.createWritePromise(createEcho(message), { + route: toProtobufRoute(options?.route), + decoder: options?.decoder, + }); } - /** Returns the server time. - * See https://valkey.io/commands/time/ for details. + /** + * Returns the server time. * - * @param route - The command will be routed to a random node, unless `route` is provided, in which - * case the client will route the command to the nodes defined by `route`. + * The command will be routed to a random node, unless `route` is provided. + * + * @see {@link https://valkey.io/commands/time/|valkey.io} for details. + * + * @param options - (Optional) See {@link RouteOption}. + * + * @returns The current server time as an `array` with two items: + * - A Unix timestamp, + * - The amount of microseconds already elapsed in the current second. * - * @returns - The current server time as a two items `array`: - * A Unix timestamp and the amount of microseconds already elapsed in the current second. - * The returned `array` is in a [Unix timestamp, Microseconds already elapsed] format. * When specifying a route other than a single node, it returns a dictionary where each address is the key and * its corresponding node response is the value. * @@ -564,7 +726,736 @@ export class GlideClusterClient extends BaseClient { * console.log(result); // Output: {'addr': ['1710925775', '913580'], 'addr2': ['1710925775', '913580'], 'addr3': ['1710925775', '913580']} * ``` */ - public time(route?: Routes): Promise> { - return this.createWritePromise(createTime(), toProtobufRoute(route)); + public async time( + options?: RouteOption, + ): Promise> { + return this.createWritePromise(createTime(), { + route: toProtobufRoute(options?.route), + decoder: Decoder.String, + }); + } + + /** + * Copies the value stored at the `source` to the `destination` key. When `replace` is `true`, + * removes the `destination` key first if it already exists, otherwise performs no action. + * + * @see {@link https://valkey.io/commands/copy/|valkey.io} for details. + * @remarks When in cluster mode, `source` and `destination` must map to the same hash slot. + * @remarks Since Valkey version 6.2.0. + * + * @param source - The key to the source value. + * @param destination - The key where the value should be copied to. + * @param replace - (Optional) If `true`, the `destination` key should be removed before copying the + * value to it. If not provided, no action will be performed if the key already exists. + * @returns `true` if `source` was copied, `false` if the `source` was not copied. + * + * @example + * ```typescript + * const result = await client.copy("set1", "set2", true); + * console.log(result); // Output: true - "set1" was copied to "set2". + * ``` + */ + public async copy( + source: GlideString, + destination: GlideString, + replace?: boolean, + ): Promise { + return this.createWritePromise( + createCopy(source, destination, { replace: replace }), + ); + } + + /** + * Displays a piece of generative computer art and the server version. + * + * The command will be routed to a random node, unless `route` is provided. + * + * @see {@link https://valkey.io/commands/lolwut/|valkey.io} for details. + * + * @param options - (Optional) The LOLWUT options - see {@link LolwutOptions} and {@link RouteOption}. + * @returns A piece of generative computer art along with the current server version. + * + * @example + * ```typescript + * const response = await client.lolwut({ version: 6, parameters: [40, 20] }, "allNodes"); + * console.log(response); // Output: "Redis ver. 7.2.3" - Indicates the current server version. + * ``` + */ + public async lolwut( + options?: LolwutOptions & RouteOption, + ): Promise> { + return this.createWritePromise(createLolwut(options), { + route: toProtobufRoute(options?.route), + decoder: Decoder.String, + }); + } + + /** + * Invokes a previously loaded function. + * + * @see {@link https://valkey.io/commands/fcall/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param func - The function name. + * @param args - A list of `function` arguments and it should not represent names of keys. + * @param options - (Optional) See {@link RouteOption} and {@link DecoderOption}. + * @returns The invoked function's return value. + * + * @example + * ```typescript + * const response = await client.fcallWithRoute("Deep_Thought", [], "randomNode"); + * console.log(response); // Output: Returns the function's return value. + * ``` + */ + public async fcallWithRoute( + func: GlideString, + args: GlideString[], + options?: RouteOption & DecoderOption, + ): Promise> { + return this.createWritePromise(createFCall(func, [], args), { + route: toProtobufRoute(options?.route), + decoder: options?.decoder, + }); + } + + /** + * Invokes a previously loaded read-only function. + * + * @see {@link https://valkey.io/commands/fcall/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param func - The function name. + * @param args - A list of `function` arguments and it should not represent names of keys. + * @param options - (Optional) See {@link RouteOption} and {@link DecoderOption}. + * @returns The invoked function's return value. + * + * @example + * ```typescript + * const response = await client.fcallReadonlyWithRoute("Deep_Thought", ["Answer", "to", "the", "Ultimate", + * "Question", "of", "Life,", "the", "Universe,", "and", "Everything"], "randomNode"); + * console.log(response); // Output: 42 # The return value on the function that was execute. + * ``` + */ + public async fcallReadonlyWithRoute( + func: GlideString, + args: GlideString[], + options?: RouteOption & DecoderOption, + ): Promise> { + return this.createWritePromise(createFCallReadOnly(func, [], args), { + route: toProtobufRoute(options?.route), + decoder: options?.decoder, + }); + } + + /** + * Deletes a library and all its functions. + * + * @see {@link https://valkey.io/commands/function-delete/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param libraryCode - The library name to delete. + * @param route - (Optional) The command will be routed to all primary node, unless `route` is provided, in which + * case the client will route the command to the nodes defined by `route`. + * @returns A simple `"OK"` response. + * + * @example + * ```typescript + * const result = await client.functionDelete("libName"); + * console.log(result); // Output: 'OK' + * ``` + */ + public async functionDelete( + libraryCode: GlideString, + route?: Routes, + ): Promise<"OK"> { + return this.createWritePromise(createFunctionDelete(libraryCode), { + route: toProtobufRoute(route), + decoder: Decoder.String, + }); + } + + /** + * Loads a library to Valkey. + * + * @see {@link https://valkey.io/commands/function-load/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param libraryCode - The source code that implements the library. + * @param options - (Optional) Additional parameters: + * - (Optional) `replace`: whether the given library should overwrite a library with the same name if it + * already exists. + * - (Optional) `route`: see {@link RouteOption}. + * - (Optional) `decoder`: see {@link DecoderOption}. + * @returns The library name that was loaded. + * + * @example + * ```typescript + * const code = "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return args[1] end)"; + * const result = await client.functionLoad(code, true, 'allNodes'); + * console.log(result); // Output: 'mylib' + * ``` + */ + public async functionLoad( + libraryCode: GlideString, + options?: { + replace?: boolean; + } & RouteOption & + DecoderOption, + ): Promise { + return this.createWritePromise( + createFunctionLoad(libraryCode, options?.replace), + { + route: toProtobufRoute(options?.route), + decoder: options?.decoder, + }, + ); + } + + /** + * Deletes all function libraries. + * + * @see {@link https://valkey.io/commands/function-flush/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param options - (Optional) Additional parameters: + * - (Optional) `mode`: the flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}. + * - (Optional) `route`: see {@link RouteOption}. + * @returns A simple `"OK"` response. + * + * @example + * ```typescript + * const result = await client.functionFlush(FlushMode.SYNC); + * console.log(result); // Output: 'OK' + * ``` + */ + public async functionFlush( + options?: { + mode?: FlushMode; + } & RouteOption, + ): Promise<"OK"> { + return this.createWritePromise(createFunctionFlush(options?.mode), { + route: toProtobufRoute(options?.route), + decoder: Decoder.String, + }); + } + + /** + * Returns information about the functions and libraries. + * + * @see {@link https://valkey.io/commands/function-list/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param options - (Optional) See {@link FunctionListOptions}, {@link DecoderOption}, and {@link RouteOption}. + * @returns Info about all or selected libraries and their functions in {@link FunctionListResponse} format. + * + * @example + * ```typescript + * // Request info for specific library including the source code + * const result1 = await client.functionList({ libNamePattern: "myLib*", withCode: true }); + * // Request info for all libraries + * const result2 = await client.functionList(); + * console.log(result2); // Output: + * // [{ + * // "library_name": "myLib5_backup", + * // "engine": "LUA", + * // "functions": [{ + * // "name": "myfunc", + * // "description": null, + * // "flags": [ "no-writes" ], + * // }], + * // "library_code": "#!lua name=myLib5_backup \n redis.register_function('myfunc', function(keys, args) return args[1] end)" + * // }] + * ``` + */ + public async functionList( + options?: FunctionListOptions & DecoderOption & RouteOption, + ): Promise> { + return this.createWritePromise(createFunctionList(options), { + route: toProtobufRoute(options?.route), + decoder: options?.decoder, + }); + } + + /** + * Returns information about the function that's currently running and information about the + * available execution engines. + * + * @see {@link https://valkey.io/commands/function-stats/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param options - (Optional) See {@link DecoderOption} and {@link RouteOption}. + * @returns A `Record` with two keys: + * - `"running_script"` with information about the running script. + * - `"engines"` with information about available engines and their stats. + * - See example for more details. + * + * @example + * ```typescript + * const response = await client.functionStats("randomNode"); + * console.log(response); // Output: + * // { + * // "running_script": + * // { + * // "name": "deep_thought", + * // "command": ["fcall", "deep_thought", "0"], + * // "duration_ms": 5008 + * // }, + * // "engines": + * // { + * // "LUA": + * // { + * // "libraries_count": 2, + * // "functions_count": 3 + * // } + * // } + * // } + * // Output if no scripts running: + * // { + * // "running_script": null + * // "engines": + * // { + * // "LUA": + * // { + * // "libraries_count": 2, + * // "functions_count": 3 + * // } + * // } + * // } + * ``` + */ + public async functionStats( + options?: RouteOption & DecoderOption, + ): Promise> { + return this.createWritePromise(createFunctionStats(), { + route: toProtobufRoute(options?.route), + decoder: options?.decoder, + }); + } + + /** + * Kills a function that is currently executing. + * `FUNCTION KILL` terminates read-only functions only. + * + * @see {@link https://valkey.io/commands/function-kill/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param route - (Optional) The client will route the command to the nodes defined by `route`. + * If not defined, the command will be routed to all nodes. + * @returns `"OK"` if function is terminated. Otherwise, throws an error. + * + * @example + * ```typescript + * await client.functionKill(); + * ``` + */ + public async functionKill(route?: Routes): Promise<"OK"> { + return this.createWritePromise(createFunctionKill(), { + route: toProtobufRoute(route), + decoder: Decoder.String, + }); + } + + /** + * Returns the serialized payload of all loaded libraries. + * + * @see {@link https://valkey.io/commands/function-dump/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param route - (Optional) The client will route the command to the nodes defined by `route`. + * If not defined, the command will be routed a random node. + * @returns The serialized payload of all loaded libraries. + * + * @example + * ```typescript + * const data = await client.functionDump(); + * // data can be used to restore loaded functions on any Valkey instance + * ``` + */ + public async functionDump( + route?: Routes, + ): Promise> { + return this.createWritePromise(createFunctionDump(), { + route: toProtobufRoute(route), + decoder: Decoder.Bytes, + }); + } + + /** + * Restores libraries from the serialized payload returned by {@link functionDump}. + * + * @see {@link https://valkey.io/commands/function-restore/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param payload - The serialized data from {@link functionDump}. + * @param options - (Optional) Additional parameters: + * - (Optional) `policy`: a policy for handling existing libraries, see {@link FunctionRestorePolicy}. + * {@link FunctionRestorePolicy.APPEND} is used by default. + * - (Optional) `route`: see {@link RouteOption}. + * @returns `"OK"`. + * + * @example + * ```typescript + * await client.functionRestore(data, { policy: FunctionRestorePolicy.FLUSH, route: "allPrimaries" }); + * ``` + */ + public async functionRestore( + payload: Buffer, + options?: { policy?: FunctionRestorePolicy } & RouteOption, + ): Promise<"OK"> { + return this.createWritePromise( + createFunctionRestore(payload, options?.policy), + { + route: toProtobufRoute(options?.route), + decoder: Decoder.String, + }, + ); + } + + /** + * Deletes all the keys of all the existing databases. This command never fails. + * + * The command will be routed to all primary nodes, unless `route` is provided. + * + * @see {@link https://valkey.io/commands/flushall/|valkey.io} for details. + * + * @param options - (Optional) Additional parameters: + * - (Optional) `mode`: the flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}. + * - (Optional) `route`: see {@link RouteOption}. + * @returns `OK`. + * + * @example + * ```typescript + * const result = await client.flushall(FlushMode.SYNC); + * console.log(result); // Output: 'OK' + * ``` + */ + public async flushall( + options?: { + mode?: FlushMode; + } & RouteOption, + ): Promise<"OK"> { + return this.createWritePromise(createFlushAll(options?.mode), { + route: toProtobufRoute(options?.route), + decoder: Decoder.String, + }); + } + + /** + * Deletes all the keys of the currently selected database. This command never fails. + * + * The command will be routed to all primary nodes, unless `route` is provided. + * + * @see {@link https://valkey.io/commands/flushdb/|valkey.io} for details. + * + * @param options - (Optional) Additional parameters: + * - (Optional) `mode`: the flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}. + * - (Optional) `route`: see {@link RouteOption}. + * @returns `OK`. + * + * @example + * ```typescript + * const result = await client.flushdb(FlushMode.SYNC); + * console.log(result); // Output: 'OK' + * ``` + */ + public async flushdb( + options?: { + mode?: FlushMode; + } & RouteOption, + ): Promise<"OK"> { + return this.createWritePromise(createFlushDB(options?.mode), { + route: toProtobufRoute(options?.route), + decoder: Decoder.String, + }); + } + + /** + * Returns the number of keys in the database. + * + * The command will be routed to all nodes, unless `route` is provided. + * + * @see {@link https://valkey.io/commands/dbsize/|valkey.io} for details. + * + * @param options - (Optional) See {@link RouteOption}. + * @returns The number of keys in the database. + * In the case of routing the query to multiple nodes, returns the aggregated number of keys across the different nodes. + * + * @example + * ```typescript + * const numKeys = await client.dbsize("allPrimaries"); + * console.log("Number of keys across all primary nodes: ", numKeys); + * ``` + */ + public async dbsize( + options?: RouteOption, + ): Promise> { + return this.createWritePromise(createDBSize(), { + route: toProtobufRoute(options?.route), + }); + } + + /** Publish a message on pubsub channel. + * This command aggregates PUBLISH and SPUBLISH commands functionalities. + * The mode is selected using the 'sharded' parameter. + * For both sharded and non-sharded mode, request is routed using hashed channel as key. + * + * @see {@link https://valkey.io/commands/publish} and {@link https://valkey.io/commands/spublish} for more details. + * + * @param message - Message to publish. + * @param channel - Channel to publish the message on. + * @param sharded - Use sharded pubsub mode. Available since Valkey version 7.0. + * @returns - Number of subscriptions in primary node that received the message. + * + * @example + * ```typescript + * // Example usage of publish command + * const result = await client.publish("Hi all!", "global-channel"); + * console.log(result); // Output: 1 - This message was posted to 1 subscription which is configured on primary node + * ``` + * + * @example + * ```typescript + * // Example usage of spublish command + * const result = await client.publish("Hi all!", "global-channel", true); + * console.log(result); // Output: 2 - Published 2 instances of "Hi to sharded channel1!" message on channel1 using sharded mode + * ``` + */ + public async publish( + message: GlideString, + channel: GlideString, + sharded: boolean = false, + ): Promise { + return this.createWritePromise( + createPublish(message, channel, sharded), + ); + } + + /** + * Lists the currently active shard channels. + * The command is routed to all nodes, and aggregates the response to a single array. + * + * @see {@link https://valkey.io/commands/pubsub-shardchannels/|valkey.io} for details. + * + * @param options - (Optional) Additional parameters: + * - (Optional) `pattern`: A glob-style pattern to match active shard channels. + * If not provided, all active shard channels are returned. + * - (Optional) `decoder`: see {@link DecoderOption}. + * @returns A list of currently active shard channels matching the given pattern. + * If no pattern is specified, all active shard channels are returned. + * + * @example + * ```typescript + * const allChannels = await client.pubsubShardchannels(); + * console.log(allChannels); // Output: ["channel1", "channel2"] + * + * const filteredChannels = await client.pubsubShardchannels("channel*"); + * console.log(filteredChannels); // Output: ["channel1", "channel2"] + * ``` + */ + public async pubsubShardChannels( + options?: { + pattern?: GlideString; + } & DecoderOption, + ): Promise { + return this.createWritePromise( + createPubsubShardChannels(options?.pattern), + { decoder: options?.decoder }, + ); + } + + /** + * Returns the number of subscribers (exclusive of clients subscribed to patterns) for the specified shard channels. + * + * Note that it is valid to call this command without channels. In this case, it will just return an empty map. + * The command is routed to all nodes, and aggregates the response to a single map of the channels and their number of subscriptions. + * + * @see {@link https://valkey.io/commands/pubsub-shardnumsub/|valkey.io} for details. + * + * @param channels - The list of shard channels to query for the number of subscribers. + * If not provided, returns an empty map. + * @returns A map where keys are the shard channel names and values are the number of subscribers. + * + * @example + * ```typescript + * const result1 = await client.pubsubShardnumsub(["channel1", "channel2"]); + * console.log(result1); // Output: { "channel1": 3, "channel2": 5 } + * + * const result2 = await client.pubsubShardnumsub(); + * console.log(result2); // Output: {} + * ``` + */ + public async pubsubShardNumSub( + channels?: string[], + ): Promise> { + return this.createWritePromise(createPubSubShardNumSub(channels)); + } + + /** + * Sorts the elements in the list, set, or sorted set at `key` and returns the result. + * + * The `sort` command can be used to sort elements based on different criteria and + * apply transformations on sorted elements. + * + * To store the result into a new key, see {@link sortStore}. + * + * @see {@link https://valkey.io/commands/sort/|valkey.io} for details. + * + * @param key - The key of the list, set, or sorted set to be sorted. + * @param options - (Optional) {@link SortClusterOptions} and {@link DecoderOption}. + * @returns An `Array` of sorted elements. + * + * @example + * ```typescript + * await client.lpush("mylist", ["3", "1", "2", "a"]); + * const result = await client.sort("mylist", { alpha: true, orderBy: SortOrder.DESC, limit: { offset: 0, count: 3 } }); + * console.log(result); // Output: [ 'a', '3', '2' ] - List is sorted in descending order lexicographically + * ``` + */ + public async sort( + key: GlideString, + options?: SortClusterOptions & DecoderOption, + ): Promise { + return this.createWritePromise(createSort(key, options), { + decoder: options?.decoder, + }); + } + + /** + * Sorts the elements in the list, set, or sorted set at `key` and returns the result. + * + * The `sortReadOnly` command can be used to sort elements based on different criteria and + * apply transformations on sorted elements. + * + * This command is routed depending on the client's {@link ReadFrom} strategy. + * + * @remarks Since Valkey version 7.0.0. + * + * @param key - The key of the list, set, or sorted set to be sorted. + * @param options - (Optional) {@link SortClusterOptions} and {@link DecoderOption}. + * @returns An `Array` of sorted elements + * + * @example + * ```typescript + * await client.lpush("mylist", ["3", "1", "2", "a"]); + * const result = await client.sortReadOnly("mylist", { alpha: true, orderBy: SortOrder.DESC, limit: { offset: 0, count: 3 } }); + * console.log(result); // Output: [ 'a', '3', '2' ] - List is sorted in descending order lexicographically + * ``` + */ + public async sortReadOnly( + key: GlideString, + options?: SortClusterOptions & DecoderOption, + ): Promise { + return this.createWritePromise(createSortReadOnly(key, options), { + decoder: options?.decoder, + }); + } + + /** + * Sorts the elements in the list, set, or sorted set at `key` and stores the result in + * `destination`. + * + * The `sort` command can be used to sort elements based on different criteria and + * apply transformations on sorted elements, and store the result in a new key. + * + * To get the sort result without storing it into a key, see {@link sort} or {@link sortReadOnly}. + * + * @see {@link https://valkey.io/commands/sort/|valkey.io} for details. + * @remarks When in cluster mode, `destination` and `key` must map to the same hash slot. + * + * @param key - The key of the list, set, or sorted set to be sorted. + * @param destination - The key where the sorted result will be stored. + * @param options - (Optional) {@link SortClusterOptions}. + * @returns The number of elements in the sorted key stored at `destination`. + * + * @example + * ```typescript + * await client.lpush("mylist", ["3", "1", "2", "a"]); + * const sortedElements = await client.sortReadOnly("mylist", "sortedList", { alpha: true, orderBy: SortOrder.DESC, limit: { offset: 0, count: 3 } }); + * console.log(sortedElements); // Output: 3 - number of elements sorted and stored + * console.log(await client.lrange("sortedList", 0, -1)); // Output: [ 'a', '3', '2' ] - List is sorted in descending order lexicographically and stored in `sortedList` + * ``` + */ + public async sortStore( + key: GlideString, + destination: GlideString, + options?: SortClusterOptions, + ): Promise { + return this.createWritePromise(createSort(key, options, destination)); + } + + /** + * Returns `UNIX TIME` of the last DB save timestamp or startup timestamp if no save + * was made since then. + * + * The command will be routed to a random node, unless `route` is provided. + * + * @see {@link https://valkey.io/commands/lastsave/|valkey.io} for details. + * + * @param options - (Optional) See {@link RouteOption}. + * @returns `UNIX TIME` of the last DB save executed with success. + * + * @example + * ```typescript + * const timestamp = await client.lastsave(); + * console.log("Last DB save was done at " + timestamp); + * ``` + */ + public async lastsave( + options?: RouteOption, + ): Promise> { + return this.createWritePromise(createLastSave(), { + route: toProtobufRoute(options?.route), + }); + } + + /** + * Returns a random existing key name. + * + * The command will be routed to all primary nodes, unless `route` is provided. + * + * @see {@link https://valkey.io/commands/randomkey/|valkey.io} for details. + * + * @param options - (Optional) See {@link RouteOption} and {@link DecoderOption}. + * @returns A random existing key name. + * + * @example + * ```typescript + * const result = await client.randomKey(); + * console.log(result); // Output: "key12" - "key12" is a random existing key name. + * ``` + */ + public async randomKey( + options?: DecoderOption & RouteOption, + ): Promise { + return this.createWritePromise(createRandomKey(), { + route: toProtobufRoute(options?.route), + decoder: options?.decoder, + }); + } + + /** + * Flushes all the previously watched keys for a transaction. Executing a transaction will + * automatically flush all previously watched keys. + * + * The command will be routed to all primary nodes, unless `route` is provided + * + * @see {@link https://valkey.io/commands/unwatch/|valkey.io} and {@link https://valkey.io/topics/transactions/#cas|Valkey Glide Wiki} for more details. + * + * @param options - (Optional) See {@link RouteOption}. + * @returns A simple `"OK"` response. + * + * @example + * ```typescript + * let response = await client.watch(["sampleKey"]); + * console.log(response); // Output: "OK" + * response = await client.unwatch(); + * console.log(response); // Output: "OK" + * ``` + */ + public async unwatch(options?: RouteOption): Promise<"OK"> { + return this.createWritePromise(createUnWatch(), { + route: toProtobufRoute(options?.route), + decoder: Decoder.String, + }); } } diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 436ff33486..1547f52c59 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -2,117 +2,265 @@ * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +import { + BaseClient, // eslint-disable-line @typescript-eslint/no-unused-vars + GlideString, + HashDataType, + convertFieldsAndValuesForHset, +} from "./BaseClient"; + import { AggregationType, + BaseScanOptions, + BitFieldGet, + BitFieldIncrBy, // eslint-disable-line @typescript-eslint/no-unused-vars + BitFieldOverflow, // eslint-disable-line @typescript-eslint/no-unused-vars + BitFieldSet, // eslint-disable-line @typescript-eslint/no-unused-vars + BitFieldSubCommands, + BitOffset, // eslint-disable-line @typescript-eslint/no-unused-vars + BitOffsetMultiplier, // eslint-disable-line @typescript-eslint/no-unused-vars + BitOffsetOptions, + BitmapIndexType, + BitwiseOperation, + Boundary, + CoordOrigin, // eslint-disable-line @typescript-eslint/no-unused-vars ExpireOptions, + FlushMode, + FunctionListOptions, + FunctionListResponse, // eslint-disable-line @typescript-eslint/no-unused-vars + FunctionRestorePolicy, + FunctionStatsSingleResponse, // eslint-disable-line @typescript-eslint/no-unused-vars + GeoAddOptions, + GeoBoxShape, // eslint-disable-line @typescript-eslint/no-unused-vars + GeoCircleShape, // eslint-disable-line @typescript-eslint/no-unused-vars + GeoSearchResultOptions, + GeoSearchShape, + GeoSearchStoreResultOptions, + GeoUnit, + GeospatialData, InfoOptions, InsertPosition, KeyWeight, + LPosOptions, + ListDirection, + LolwutOptions, + MemberOrigin, // eslint-disable-line @typescript-eslint/no-unused-vars RangeByIndex, RangeByLex, RangeByScore, - ScoreBoundary, + RestoreOptions, + ReturnTypeXinfoStream, // eslint-disable-line @typescript-eslint/no-unused-vars + ScoreFilter, + SearchOrigin, SetOptions, + SortClusterOptions, + SortOptions, StreamAddOptions, + StreamClaimOptions, + StreamGroupOptions, + StreamPendingOptions, + StreamReadGroupOptions, StreamReadOptions, StreamTrimOptions, + TimeUnit, ZAddOptions, + createAppend, + createBLMPop, + createBLMove, createBLPop, createBRPop, + createBZMPop, + createBZPopMax, + createBZPopMin, + createBitCount, + createBitField, + createBitOp, + createBitPos, createClientGetName, createClientId, createConfigGet, createConfigResetStat, createConfigRewrite, createConfigSet, + createCopy, createCustomCommand, + createDBSize, createDecr, createDecrBy, createDel, + createDump, createEcho, createExists, createExpire, createExpireAt, + createExpireTime, + createFCall, + createFCallReadOnly, + createFlushAll, + createFlushDB, + createFunctionDelete, + createFunctionDump, + createFunctionFlush, + createFunctionList, + createFunctionLoad, + createFunctionRestore, + createFunctionStats, + createGeoAdd, + createGeoDist, + createGeoHash, + createGeoPos, + createGeoSearch, + createGeoSearchStore, createGet, + createGetBit, + createGetDel, + createGetEx, + createGetRange, createHDel, createHExists, createHGet, createHGetAll, createHIncrBy, createHIncrByFloat, + createHKeys, createHLen, createHMGet, + createHRandField, + createHScan, createHSet, createHSetNX, + createHStrlen, createHVals, createIncr, createIncrBy, createIncrByFloat, createInfo, + createLCS, createLIndex, createLInsert, createLLen, + createLMPop, + createLMove, createLPop, + createLPos, createLPush, + createLPushX, createLRange, createLRem, createLSet, createLTrim, + createLastSave, + createLolwut, createMGet, createMSet, + createMSetNX, + createMove, createObjectEncoding, createObjectFreq, createObjectIdletime, createObjectRefcount, createPExpire, createPExpireAt, + createPExpireTime, createPTTL, createPersist, createPfAdd, createPfCount, + createPfMerge, createPing, + createPubSubChannels, + createPubSubNumPat, + createPubSubNumSub, + createPubSubShardNumSub, + createPublish, + createPubsubShardChannels, createRPop, createRPush, + createRPushX, + createRandomKey, createRename, createRenameNX, + createRestore, createSAdd, createSCard, createSDiff, createSDiffStore, createSInter, + createSInterCard, createSInterStore, createSIsMember, + createSMIsMember, createSMembers, createSMove, createSPop, + createSRandMember, createSRem, + createSScan, + createSUnion, createSUnionStore, createSelect, createSet, + createSetBit, + createSetRange, + createSort, + createSortReadOnly, createStrlen, createTTL, createTime, + createTouch, createType, createUnlink, + createWait, + createXAck, createXAdd, + createXAutoClaim, + createXClaim, + createXDel, + createXGroupCreate, + createXGroupCreateConsumer, + createXGroupDelConsumer, + createXGroupDestroy, + createXGroupSetid, + createXInfoConsumers, + createXInfoGroups, + createXInfoStream, createXLen, + createXPending, + createXRange, createXRead, + createXReadGroup, + createXRevRange, createXTrim, createZAdd, createZCard, createZCount, + createZDiff, + createZDiffStore, + createZDiffWithScores, + createZIncrBy, + createZInter, createZInterCard, createZInterstore, + createZLexCount, + createZMPop, + createZMScore, createZPopMax, createZPopMin, + createZRandMember, createZRange, + createZRangeStore, createZRangeWithScores, createZRank, createZRem, + createZRemRangeByLex, createZRemRangeByRank, createZRemRangeByScore, + createZRevRank, + createZRevRankWithScore, + createZScan, createZScore, - createSUnion, + createZUnion, + createZUnionStore, } from "./Commands"; import { command_request } from "./ProtobufMessage"; @@ -166,18 +314,70 @@ export class BaseTransaction> { } /** Get the value associated with the given key, or null if no such value exists. - * See https://valkey.io/commands/get/ for details. + * @see {@link https://valkey.io/commands/get/|valkey.io} for details. * * @param key - The key to retrieve from the database. * - * Command Response - If `key` exists, returns the value of `key` as a string. Otherwise, return null. + * Command Response - If `key` exists, returns the value of `key`. Otherwise, return null. */ - public get(key: string): T { + public get(key: GlideString): T { return this.addAndReturn(createGet(key)); } + /** + * Get the value of `key` and optionally set its expiration. `GETEX` is similar to {@link get}. + * + * @see {@link https://valkey.io/commands/getex/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key to retrieve from the database. + * @param options - (Optional) set expiriation to the given key. + * "persist" will retain the time to live associated with the key. Equivalent to `PERSIST` in the VALKEY API. + * Otherwise, a {@link TimeUnit} and duration of the expire time should be specified. + * + * Command Response - If `key` exists, returns the value of `key` as a `string`. Otherwise, return `null`. + */ + public getex( + key: GlideString, + options?: "persist" | { type: TimeUnit; duration: number }, + ): T { + return this.addAndReturn(createGetEx(key, options)); + } + + /** + * Gets a string value associated with the given `key`and deletes the key. + * + * @see {@link https://valkey.io/commands/getdel/|valkey.io} for details. + * + * @param key - The key to retrieve from the database. + * + * Command Response - If `key` exists, returns the `value` of `key`. Otherwise, return `null`. + */ + public getdel(key: GlideString): T { + return this.addAndReturn(createGetDel(key)); + } + + /** + * Returns the substring of the string value stored at `key`, determined by the offsets + * `start` and `end` (both are inclusive). Negative offsets can be used in order to provide + * an offset starting from the end of the string. So `-1` means the last character, `-2` the + * penultimate and so forth. If `key` does not exist, an empty string is returned. If `start` + * or `end` are out of range, returns the substring within the valid range of the string. + * + * @see {@link https://valkey.io/commands/getrange/|valkey.io} for details. + * + * @param key - The key of the string. + * @param start - The starting offset. + * @param end - The ending offset. + * + * Command Response - substring extracted from the value stored at `key`. + */ + public getrange(key: GlideString, start: number, end: number): T { + return this.addAndReturn(createGetRange(key, start, end)); + } + /** Set the given key with the given value. Return value is dependent on the passed options. - * See https://valkey.io/commands/set/ for details. + * @see {@link https://valkey.io/commands/set/|valkey.io} for details. * * @param key - The key to store. * @param value - The value to store with the given key. @@ -187,57 +387,104 @@ export class BaseTransaction> { * If `value` isn't set because of `onlyIfExists` or `onlyIfDoesNotExist` conditions, return null. * If `returnOldValue` is set, return the old value as a string. */ - public set(key: string, value: string, options?: SetOptions): T { + public set(key: GlideString, value: GlideString, options?: SetOptions): T { return this.addAndReturn(createSet(key, value, options)); } - /** Ping the Redis server. - * See https://valkey.io/commands/ping/ for details. + /** + * Pings the server. + * + * @see {@link https://valkey.io/commands/ping/|valkey.io} for details. * - * @param message - An optional message to include in the PING command. - * If not provided, the server will respond with "PONG". - * If provided, the server will respond with a copy of the message. + * @param message - (Optional) A message to include in the PING command. + * - If not provided, the server will respond with `"PONG"`. + * - If provided, the server will respond with a copy of the message. * - * Command Response - "PONG" if `message` is not provided, otherwise return a copy of `message`. + * Command Response - `"PONG"` if `message` is not provided, otherwise return a copy of `message`. */ - public ping(message?: string): T { + public ping(message?: GlideString): T { return this.addAndReturn(createPing(message)); } - /** Get information and statistics about the Redis server. - * See https://valkey.io/commands/info/ for details. + /** + * Gets information and statistics about the server. + * + * @see {@link https://valkey.io/commands/info/|valkey.io} for details. * - * @param options - A list of InfoSection values specifying which sections of information to retrieve. - * When no parameter is provided, the default option is assumed. + * @param sections - (Optional) A list of {@link InfoOptions} values specifying which sections of information to retrieve. + * When no parameter is provided, {@link InfoOptions.Default|Default} is assumed. * - * Command Response - a string containing the information for the sections requested. + * Command Response - A string containing the information for the sections requested. */ - public info(options?: InfoOptions[]): T { - return this.addAndReturn(createInfo(options)); + public info(sections?: InfoOptions[]): T { + return this.addAndReturn(createInfo(sections)); } - /** Remove the specified keys. A key is ignored if it does not exist. - * See https://valkey.io/commands/del/ for details. + /** + * Removes the specified keys. A key is ignored if it does not exist. + * + * @see {@link https://valkey.io/commands/del/|valkey.io} for details. * * @param keys - A list of keys to be deleted from the database. * - * Command Response - the number of keys that were removed. + * Command Response - The number of keys that were removed. */ - public del(keys: string[]): T { + public del(keys: GlideString[]): T { return this.addAndReturn(createDel(keys)); } - /** Get the name of the connection on which the transaction is being executed. - * See https://valkey.io/commands/client-getname/ for more details. + /** + * Serialize the value stored at `key` in a Valkey-specific format and return it to the user. + * + * @see {@link https://valkey.io/commands/dump/|valkey.io} for details. + * @remarks To execute a transaction with a `dump` command, the `exec` command requires `Decoder.Bytes` to handle the response. + * + * @param key - The `key` to serialize. + * + * Command Response - The serialized value of the data stored at `key`. If `key` does not exist, `null` will be returned. + */ + public dump(key: GlideString): T { + return this.addAndReturn(createDump(key)); + } + + /** + * Create a `key` associated with a `value` that is obtained by deserializing the provided + * serialized `value` (obtained via {@link dump}). + * + * @see {@link https://valkey.io/commands/restore/|valkey.io} for details. + * @remarks `options.idletime` and `options.frequency` modifiers cannot be set at the same time. + * + * @param key - The `key` to create. + * @param ttl - The expiry time (in milliseconds). If `0`, the `key` will persist. + * @param value - The serialized value to deserialize and assign to `key`. + * @param options - (Optional) Restore options {@link RestoreOptions}. + * + * Command Response - Return "OK" if the `key` was successfully restored with a `value`. + */ + public restore( + key: GlideString, + ttl: number, + value: Buffer, + options?: RestoreOptions, + ): T { + return this.addAndReturn(createRestore(key, ttl, value, options)); + } + + /** + * Gets the name of the connection on which the transaction is being executed. * - * Command Response - the name of the client connection as a string if a name is set, or null if no name is assigned. + * @see {@link https://valkey.io/commands/client-getname/|valkey.io} for details. + * + * Command Response - The name of the client connection as a string if a name is set, or null if no name is assigned. */ public clientGetName(): T { return this.addAndReturn(createClientGetName()); } - /** Rewrite the configuration file with the current configuration. - * See https://valkey.io/commands/select/ for details. + /** + * Rewrites the configuration file with the current configuration. + * + * @see {@link https://valkey.io/commands/select/|valkey.io} for details. * * Command Response - "OK" when the configuration was rewritten properly. Otherwise, the transaction fails with an error. */ @@ -245,8 +492,10 @@ export class BaseTransaction> { return this.addAndReturn(createConfigRewrite()); } - /** Resets the statistics reported by Redis using the INFO and LATENCY HISTOGRAM commands. - * See https://valkey.io/commands/config-resetstat/ for details. + /** + * Resets the statistics reported by Redis using the `INFO` and `LATENCY HISTOGRAM` commands. + * + * @see {@link https://valkey.io/commands/config-resetstat/|valkey.io} for details. * * Command Response - always "OK". */ @@ -255,19 +504,19 @@ export class BaseTransaction> { } /** Retrieve the values of multiple keys. - * See https://valkey.io/commands/mget/ for details. + * @see {@link https://valkey.io/commands/mget/|valkey.io} for details. * * @param keys - A list of keys to retrieve values for. * * Command Response - A list of values corresponding to the provided keys. If a key is not found, * its corresponding value in the list will be null. */ - public mget(keys: string[]): T { + public mget(keys: GlideString[]): T { return this.addAndReturn(createMGet(keys)); } /** Set multiple keys to multiple values in a single atomic operation. - * See https://valkey.io/commands/mset/ for details. + * @see {@link https://valkey.io/commands/mset/|valkey.io} for details. * * @param keyValueMap - A key-value map consisting of keys and their respective values to set. * @@ -277,33 +526,46 @@ export class BaseTransaction> { return this.addAndReturn(createMSet(keyValueMap)); } + /** + * Sets multiple keys to values if the key does not exist. The operation is atomic, and if one or + * more keys already exist, the entire operation fails. + * + * @see {@link https://valkey.io/commands/msetnx/|valkey.io} for details. + * + * @param keyValueMap - A key-value map consisting of keys and their respective values to set. + * Command Response - `true` if all keys were set. `false` if no key was set. + */ + public msetnx(keyValueMap: Record): T { + return this.addAndReturn(createMSetNX(keyValueMap)); + } + /** Increments the number stored at `key` by one. If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/incr/ for details. + * @see {@link https://valkey.io/commands/incr/|valkey.io} for details. * * @param key - The key to increment its value. * * Command Response - the value of `key` after the increment. */ - public incr(key: string): T { + public incr(key: GlideString): T { return this.addAndReturn(createIncr(key)); } /** Increments the number stored at `key` by `amount`. If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/incrby/ for details. + * @see {@link https://valkey.io/commands/incrby/|valkey.io} for details. * * @param key - The key to increment its value. * @param amount - The amount to increment. * * Command Response - the value of `key` after the increment. */ - public incrBy(key: string, amount: number): T { + public incrBy(key: GlideString, amount: number): T { return this.addAndReturn(createIncrBy(key, amount)); } /** Increment the string representing a floating point number stored at `key` by `amount`. * By using a negative amount value, the result is that the value stored at `key` is decremented. * If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/incrbyfloat/ for details. + * @see {@link https://valkey.io/commands/incrbyfloat/|valkey.io} for details. * * @param key - The key to increment its value. * @param amount - The amount to increment. @@ -311,44 +573,199 @@ export class BaseTransaction> { * Command Response - the value of `key` after the increment. * */ - public incrByFloat(key: string, amount: number): T { + public incrByFloat(key: GlideString, amount: number): T { return this.addAndReturn(createIncrByFloat(key, amount)); } - /** Returns the current connection id. - * See https://valkey.io/commands/client-id/ for details. + /** + * Returns the current connection ID. + * + * @see {@link https://valkey.io/commands/client-id/|valkey.io} for details. * - * Command Response - the id of the client. + * Command Response - The ID of the connection. */ public clientId(): T { return this.addAndReturn(createClientId()); } /** Decrements the number stored at `key` by one. If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/decr/ for details. + * @see {@link https://valkey.io/commands/decr/|valkey.io} for details. * * @param key - The key to decrement its value. * * Command Response - the value of `key` after the decrement. */ - public decr(key: string): T { + public decr(key: GlideString): T { return this.addAndReturn(createDecr(key)); } /** Decrements the number stored at `key` by `amount`. If `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/decrby/ for details. + * @see {@link https://valkey.io/commands/decrby/|valkey.io} for details. * * @param key - The key to decrement its value. * @param amount - The amount to decrement. * * Command Response - the value of `key` after the decrement. */ - public decrBy(key: string, amount: number): T { + public decrBy(key: GlideString, amount: number): T { return this.addAndReturn(createDecrBy(key, amount)); } - /** Reads the configuration parameters of a running Redis server. - * See https://valkey.io/commands/config-get/ for details. + /** + * Perform a bitwise operation between multiple keys (containing string values) and store the result in the + * `destination`. + * + * @see {@link https://valkey.io/commands/bitop/|valkey.io} for details. + * + * @param operation - The bitwise operation to perform. + * @param destination - The key that will store the resulting string. + * @param keys - The list of keys to perform the bitwise operation on. + * + * Command Response - The size of the string stored in `destination`. + */ + public bitop( + operation: BitwiseOperation, + destination: GlideString, + keys: GlideString[], + ): T { + return this.addAndReturn(createBitOp(operation, destination, keys)); + } + + /** + * Returns the bit value at `offset` in the string value stored at `key`. `offset` must be greater than or equal + * to zero. + * + * @see {@link https://valkey.io/commands/getbit/|valkey.io} for details. + * + * @param key - The key of the string. + * @param offset - The index of the bit to return. + * + * Command Response - The bit at the given `offset` of the string. Returns `0` if the key is empty or if the + * `offset` exceeds the length of the string. + */ + public getbit(key: GlideString, offset: number): T { + return this.addAndReturn(createGetBit(key, offset)); + } + + /** + * Sets or clears the bit at `offset` in the string value stored at `key`. The `offset` is a zero-based index, with + * `0` being the first element of the list, `1` being the next element, and so on. The `offset` must be less than + * `2^32` and greater than or equal to `0`. If a key is non-existent then the bit at `offset` is set to `value` and + * the preceding bits are set to `0`. + * + * @see {@link https://valkey.io/commands/setbit/|valkey.io} for details. + * + * @param key - The key of the string. + * @param offset - The index of the bit to be set. + * @param value - The bit value to set at `offset`. The value must be `0` or `1`. + * + * Command Response - The bit value that was previously stored at `offset`. + */ + public setbit(key: GlideString, offset: number, value: number): T { + return this.addAndReturn(createSetBit(key, offset, value)); + } + + /** + * Returns the position of the first bit matching the given `bit` value. The optional starting offset + * `start` is a zero-based index, with `0` being the first byte of the list, `1` being the next byte and so on. + * The offset can also be a negative number indicating an offset starting at the end of the list, with `-1` being + * the last byte of the list, `-2` being the penultimate, and so on. + * + * @see {@link https://valkey.io/commands/bitpos/|valkey.io} for details. + * + * @param key - The key of the string. + * @param bit - The bit value to match. Must be `0` or `1`. + * @param start - (Optional) The starting offset. If not supplied, the search will start at the beginning of the string. + * + * Command Response - The position of the first occurrence of `bit` in the binary value of the string held at `key`. + * If `start` was provided, the search begins at the offset indicated by `start`. + */ + public bitpos(key: GlideString, bit: number, start?: number): T { + return this.addAndReturn(createBitPos(key, bit, start)); + } + + /** + * Returns the position of the first bit matching the given `bit` value. The offsets are zero-based indexes, with + * `0` being the first element of the list, `1` being the next, and so on. These offsets can also be negative + * numbers indicating offsets starting at the end of the list, with `-1` being the last element of the list, `-2` + * being the penultimate, and so on. + * + * If you are using Valkey 7.0.0 or above, the optional `indexType` can also be provided to specify whether the + * `start` and `end` offsets specify BIT or BYTE offsets. If `indexType` is not provided, BYTE offsets + * are assumed. If BIT is specified, `start=0` and `end=2` means to look at the first three bits. If BYTE is + * specified, `start=0` and `end=2` means to look at the first three bytes. + * + * @see {@link https://valkey.io/commands/bitpos/|valkey.io} for details. + * + * @param key - The key of the string. + * @param bit - The bit value to match. Must be `0` or `1`. + * @param start - The starting offset. + * @param end - The ending offset. + * @param indexType - (Optional) The index offset type. This option can only be specified if you are using Valkey + * version 7.0.0 or above. Could be either {@link BitmapIndexType.BYTE} or {@link BitmapIndexType.BIT}. If no + * index type is provided, the indexes will be assumed to be byte indexes. + * + * Command Response - The position of the first occurrence from the `start` to the `end` offsets of the `bit` in the + * binary value of the string held at `key`. + */ + public bitposInterval( + key: GlideString, + bit: number, + start: number, + end: number, + indexType?: BitmapIndexType, + ): T { + return this.addAndReturn(createBitPos(key, bit, start, end, indexType)); + } + + /** + * Reads or modifies the array of bits representing the string that is held at `key` based on the specified + * `subcommands`. + * + * @see {@link https://valkey.io/commands/bitfield/|valkey.io} for details. + * + * @param key - The key of the string. + * @param subcommands - The subcommands to be performed on the binary value of the string at `key`, which could be + * any of the following: + * + * - {@link BitFieldGet} + * - {@link BitFieldSet} + * - {@link BitFieldIncrBy} + * - {@link BitFieldOverflow} + * + * Command Response - An array of results from the executed subcommands: + * + * - {@link BitFieldGet} returns the value in {@link BitOffset} or {@link BitOffsetMultiplier}. + * - {@link BitFieldSet} returns the old value in {@link BitOffset} or {@link BitOffsetMultiplier}. + * - {@link BitFieldIncrBy} returns the new value in {@link BitOffset} or {@link BitOffsetMultiplier}. + * - {@link BitFieldOverflow} determines the behavior of the {@link BitFieldSet} and {@link BitFieldIncrBy} + * subcommands when an overflow or underflow occurs. {@link BitFieldOverflow} does not return a value and + * does not contribute a value to the array response. + */ + public bitfield(key: GlideString, subcommands: BitFieldSubCommands[]): T { + return this.addAndReturn(createBitField(key, subcommands)); + } + + /** + * Reads the array of bits representing the string that is held at `key` based on the specified `subcommands`. + * + * @see {@link https://valkey.io/commands/bitfield_ro/|valkey.io} for details. + * @remarks Since Valkey version 6.0.0. + * + * @param key - The key of the string. + * @param subcommands - The {@link BitFieldGet} subcommands to be performed. + * + * Command Response - An array of results from the {@link BitFieldGet} subcommands. + * + */ + public bitfieldReadOnly(key: GlideString, subcommands: BitFieldGet[]): T { + return this.addAndReturn(createBitField(key, subcommands, true)); + } + + /** + * Reads the configuration parameters of the running server. + * + * @see {@link https://valkey.io/commands/config-get/|valkey.io} for details. * * @param parameters - A list of configuration parameter names to retrieve values for. * @@ -359,46 +776,66 @@ export class BaseTransaction> { return this.addAndReturn(createConfigGet(parameters)); } - /** Set configuration parameters to the specified values. - * See https://valkey.io/commands/config-set/ for details. + /** + * Sets configuration parameters to the specified values. * - * @param parameters - A List of keyValuePairs consisting of configuration parameters and their respective values to set. + * @see {@link https://valkey.io/commands/config-set/|valkey.io} for details. + * + * @param parameters - A map consisting of configuration parameters and their respective values to set. * * Command Response - "OK" when the configuration was set properly. Otherwise, the transaction fails with an error. */ - public configSet(parameters: Record): T { + public configSet(parameters: Record): T { return this.addAndReturn(createConfigSet(parameters)); } /** Retrieve the value associated with `field` in the hash stored at `key`. - * See https://valkey.io/commands/hget/ for details. + * @see {@link https://valkey.io/commands/hget/|valkey.io} for details. * * @param key - The key of the hash. * @param field - The field in the hash stored at `key` to retrieve from the database. * * Command Response - the value associated with `field`, or null when `field` is not present in the hash or `key` does not exist. */ - public hget(key: string, field: string): T { + public hget(key: GlideString, field: GlideString): T { return this.addAndReturn(createHGet(key, field)); } /** Sets the specified fields to their respective values in the hash stored at `key`. - * See https://valkey.io/commands/hset/ for details. + * @see {@link https://valkey.io/commands/hset/|valkey.io} for details. * * @param key - The key of the hash. - * @param fieldValueMap - A field-value map consisting of fields and their corresponding values + * @param fieldValueList - A list of field names and their values. * to be set in the hash stored at the specified key. * * Command Response - The number of fields that were added. */ - public hset(key: string, fieldValueMap: Record): T { - return this.addAndReturn(createHSet(key, fieldValueMap)); + public hset( + key: GlideString, + fieldsAndValues: HashDataType | Record, + ): T { + return this.addAndReturn( + createHSet(key, convertFieldsAndValuesForHset(fieldsAndValues)), + ); + } + + /** + * Returns all field names in the hash stored at `key`. + * + * @see {@link https://valkey.io/commands/hkeys/|valkey.io} for details. + * + * @param key - The key of the hash. + * + * Command Response - A list of field names for the hash, or an empty list when the key does not exist. + */ + public hkeys(key: GlideString): T { + return this.addAndReturn(createHKeys(key)); } /** Sets `field` in the hash stored at `key` to `value`, only if `field` does not yet exist. * If `key` does not exist, a new key holding a hash is created. * If `field` already exists, this operation has no effect. - * See https://valkey.io/commands/hsetnx/ for more details. + * @see {@link https://valkey.io/commands/hsetnx/|valkey.io} for details. * * @param key - The key of the hash. * @param field - The field to set the value for. @@ -406,13 +843,13 @@ export class BaseTransaction> { * * Command Response - `true` if the field was set, `false` if the field already existed and was not set. */ - public hsetnx(key: string, field: string, value: string): T { + public hsetnx(key: GlideString, field: GlideString, value: GlideString): T { return this.addAndReturn(createHSetNX(key, field, value)); } /** Removes the specified fields from the hash stored at `key`. * Specified fields that do not exist within this hash are ignored. - * See https://valkey.io/commands/hdel/ for details. + * @see {@link https://valkey.io/commands/hdel/|valkey.io} for details. * * @param key - The key of the hash. * @param fields - The fields to remove from the hash stored at `key`. @@ -420,12 +857,12 @@ export class BaseTransaction> { * Command Response - the number of fields that were removed from the hash, not including specified but non existing fields. * If `key` does not exist, it is treated as an empty hash and it returns 0. */ - public hdel(key: string, fields: string[]): T { + public hdel(key: GlideString, fields: GlideString[]): T { return this.addAndReturn(createHDel(key, fields)); } /** Returns the values associated with the specified fields in the hash stored at `key`. - * See https://valkey.io/commands/hmget/ for details. + * @see {@link https://valkey.io/commands/hmget/|valkey.io} for details. * * @param key - The key of the hash. * @param fields - The fields in the hash stored at `key` to retrieve from the database. @@ -434,12 +871,12 @@ export class BaseTransaction> { * For every field that does not exist in the hash, a null value is returned. * If `key` does not exist, it is treated as an empty hash and it returns a list of null values. */ - public hmget(key: string, fields: string[]): T { + public hmget(key: GlideString, fields: GlideString[]): T { return this.addAndReturn(createHMGet(key, fields)); } /** Returns if `field` is an existing field in the hash stored at `key`. - * See https://valkey.io/commands/hexists/ for details. + * @see {@link https://valkey.io/commands/hexists/|valkey.io} for details. * * @param key - The key of the hash. * @param field - The field to check in the hash stored at `key`. @@ -447,26 +884,26 @@ export class BaseTransaction> { * Command Response - `true` if the hash contains `field`. If the hash does not contain `field`, or if `key` does not exist, * the command response will be `false`. */ - public hexists(key: string, field: string): T { + public hexists(key: GlideString, field: GlideString): T { return this.addAndReturn(createHExists(key, field)); } /** Returns all fields and values of the hash stored at `key`. - * See https://valkey.io/commands/hgetall/ for details. + * @see {@link https://valkey.io/commands/hgetall/|valkey.io} for details. * * @param key - The key of the hash. * * Command Response - a map of fields and their values stored in the hash. Every field name in the map is followed by its value. * If `key` does not exist, it returns an empty map. */ - public hgetall(key: string): T { + public hgetall(key: GlideString): T { return this.addAndReturn(createHGetAll(key)); } /** Increments the number stored at `field` in the hash stored at `key` by `increment`. * By using a negative increment value, the value stored at `field` in the hash stored at `key` is decremented. * If `field` or `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/hincrby/ for details. + * @see {@link https://valkey.io/commands/hincrby/|valkey.io} for details. * * @param key - The key of the hash. * @param amount - The amount to increment. @@ -474,14 +911,14 @@ export class BaseTransaction> { * * Command Response - the value of `field` in the hash stored at `key` after the increment. */ - public hincrBy(key: string, field: string, amount: number): T { + public hincrBy(key: GlideString, field: GlideString, amount: number): T { return this.addAndReturn(createHIncrBy(key, field, amount)); } /** Increment the string representing a floating point number stored at `field` in the hash stored at `key` by `increment`. * By using a negative increment value, the value stored at `field` in the hash stored at `key` is decremented. * If `field` or `key` does not exist, it is set to 0 before performing the operation. - * See https://valkey.io/commands/hincrbyfloat/ for details. + * @see {@link https://valkey.io/commands/hincrbyfloat/|valkey.io} for details. * * @param key - The key of the hash. * @param amount - The amount to increment. @@ -489,61 +926,166 @@ export class BaseTransaction> { * * Command Response - the value of `field` in the hash stored at `key` after the increment. */ - public hincrByFloat(key: string, field: string, amount: number): T { + public hincrByFloat( + key: GlideString, + field: GlideString, + amount: number, + ): T { return this.addAndReturn(createHIncrByFloat(key, field, amount)); } /** Returns the number of fields contained in the hash stored at `key`. - * See https://valkey.io/commands/hlen/ for more details. + * @see {@link https://valkey.io/commands/hlen/|valkey.io} for details. * * @param key - The key of the hash. * * Command Response - The number of fields in the hash, or 0 when the key does not exist. */ - public hlen(key: string): T { + public hlen(key: GlideString): T { return this.addAndReturn(createHLen(key)); } /** Returns all values in the hash stored at key. - * See https://valkey.io/commands/hvals/ for more details. + * @see {@link https://valkey.io/commands/hvals/|valkey.io} for details. * * @param key - The key of the hash. * * Command Response - a list of values in the hash, or an empty list when the key does not exist. */ - public hvals(key: string): T { + public hvals(key: GlideString): T { return this.addAndReturn(createHVals(key)); } + /** + * Returns the string length of the value associated with `field` in the hash stored at `key`. + * + * @see {@link https://valkey.io/commands/hstrlen/|valkey.io} for details. + * + * @param key - The key of the hash. + * @param field - The field in the hash. + * + * Command Response - The string length or `0` if `field` or `key` does not exist. + */ + public hstrlen(key: GlideString, field: GlideString): T { + return this.addAndReturn(createHStrlen(key, field)); + } + + /** + * Returns a random field name from the hash value stored at `key`. + * + * @see {@link https://valkey.io/commands/hrandfield/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the hash. + * + * Command Response - A random field name from the hash stored at `key`, or `null` when + * the key does not exist. + */ + public hrandfield(key: GlideString): T { + return this.addAndReturn(createHRandField(key)); + } + + /** + * Iterates incrementally over a hash. + * + * @see {@link https://valkey.io/commands/hscan/|valkey.io} for more details. + * + * @param key - The key of the set. + * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of the search. + * @param options - (Optional) The {@link BaseScanOptions}. + * + * Command Response - An array of the `cursor` and the subset of the hash held by `key`. + * The first element is always the `cursor` for the next iteration of results. `"0"` will be the `cursor` + * returned on the last iteration of the hash. The second element is always an array of the subset of the + * hash held in `key`. The array in the second element is always a flattened series of string pairs, + * where the value is at even indices and the value is at odd indices. + */ + public hscan(key: string, cursor: string, options?: BaseScanOptions): T { + return this.addAndReturn(createHScan(key, cursor, options)); + } + + /** + * Retrieves up to `count` random field names from the hash value stored at `key`. + * + * @see {@link https://valkey.io/commands/hrandfield/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the hash. + * @param count - The number of field names to return. + * + * If `count` is positive, returns unique elements. If negative, allows for duplicates. + * + * Command Response - An `array` of random field names from the hash stored at `key`, + * or an `empty array` when the key does not exist. + */ + public hrandfieldCount(key: GlideString, count: number): T { + return this.addAndReturn(createHRandField(key, count)); + } + + /** + * Retrieves up to `count` random field names along with their values from the hash + * value stored at `key`. + * + * @see {@link https://valkey.io/commands/hrandfield/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the hash. + * @param count - The number of field names to return. + * + * If `count` is positive, returns unique elements. If negative, allows for duplicates. + * + * Command Response - A 2D `array` of `[fieldName, value]` `arrays`, where `fieldName` is a random + * field name from the hash and `value` is the associated value of the field name. + * If the hash does not exist or is empty, the response will be an empty `array`. + */ + public hrandfieldWithValues(key: GlideString, count: number): T { + return this.addAndReturn(createHRandField(key, count, true)); + } + /** Inserts all the specified values at the head of the list stored at `key`. * `elements` are inserted one after the other to the head of the list, from the leftmost element to the rightmost element. * If `key` does not exist, it is created as empty list before performing the push operations. - * See https://valkey.io/commands/lpush/ for details. + * @see {@link https://valkey.io/commands/lpush/|valkey.io} for details. * * @param key - The key of the list. * @param elements - The elements to insert at the head of the list stored at `key`. * * Command Response - the length of the list after the push operations. */ - public lpush(key: string, elements: string[]): T { + public lpush(key: GlideString, elements: GlideString[]): T { return this.addAndReturn(createLPush(key, elements)); } + /** + * Inserts specified values at the head of the `list`, only if `key` already + * exists and holds a list. + * + * @see {@link https://valkey.io/commands/lpushx/|valkey.io} for details. + * + * @param key - The key of the list. + * @param elements - The elements to insert at the head of the list stored at `key`. + * + * Command Response - The length of the list after the push operation. + */ + public lpushx(key: GlideString, elements: GlideString[]): T { + return this.addAndReturn(createLPushX(key, elements)); + } + /** Removes and returns the first elements of the list stored at `key`. * The command pops a single element from the beginning of the list. - * See https://valkey.io/commands/lpop/ for details. + * @see {@link https://valkey.io/commands/lpop/|valkey.io} for details. * * @param key - The key of the list. * * Command Response - The value of the first element. * If `key` does not exist null will be returned. */ - public lpop(key: string): T { + public lpop(key: GlideString): T { return this.addAndReturn(createLPop(key)); } /** Removes and returns up to `count` elements of the list stored at `key`, depending on the list's length. - * See https://valkey.io/commands/lpop/ for details. + * @see {@link https://valkey.io/commands/lpop/|valkey.io} for details. * * @param key - The key of the list. * @param count - The count of the elements to pop from the list. @@ -551,7 +1093,7 @@ export class BaseTransaction> { * Command Response - A list of the popped elements will be returned depending on the list's length. * If `key` does not exist null will be returned. */ - public lpopCount(key: string, count: number): T { + public lpopCount(key: GlideString, count: number): T { return this.addAndReturn(createLPop(key, count)); } @@ -559,7 +1101,7 @@ export class BaseTransaction> { * The offsets `start` and `end` are zero-based indexes, with 0 being the first element of the list, 1 being the next element and so on. * These offsets can also be negative numbers indicating offsets starting at the end of the list, * with -1 being the last element of the list, -2 being the penultimate, and so on. - * See https://valkey.io/commands/lrange/ for details. + * @see {@link https://valkey.io/commands/lrange/|valkey.io} for details. * * @param key - The key of the list. * @param start - The starting point of the range. @@ -570,29 +1112,87 @@ export class BaseTransaction> { * If `end` exceeds the actual end of the list, the range will stop at the actual end of the list. * If `key` does not exist an empty list will be returned. */ - public lrange(key: string, start: number, end: number): T { + public lrange(key: GlideString, start: number, end: number): T { return this.addAndReturn(createLRange(key, start, end)); } /** Returns the length of the list stored at `key`. - * See https://valkey.io/commands/llen/ for details. + * @see {@link https://valkey.io/commands/llen/|valkey.io} for details. * * @param key - The key of the list. * * Command Response - the length of the list at `key`. * If `key` does not exist, it is interpreted as an empty list and 0 is returned. */ - public llen(key: string): T { + public llen(key: GlideString): T { return this.addAndReturn(createLLen(key)); } + /** + * Atomically pops and removes the left/right-most element to the list stored at `source` + * depending on `whereFrom`, and pushes the element at the first/last element of the list + * stored at `destination` depending on `whereTo`, see {@link ListDirection}. + * + * @see {@link https://valkey.io/commands/lmove/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param source - The key to the source list. + * @param destination - The key to the destination list. + * @param whereFrom - The {@link ListDirection} to remove the element from. + * @param whereTo - The {@link ListDirection} to add the element to. + * + * Command Response - The popped element, or `null` if `source` does not exist. + */ + public lmove( + source: GlideString, + destination: GlideString, + whereFrom: ListDirection, + whereTo: ListDirection, + ): T { + return this.addAndReturn( + createLMove(source, destination, whereFrom, whereTo), + ); + } + + /** + * + * Blocks the connection until it pops atomically and removes the left/right-most element to the + * list stored at `source` depending on `whereFrom`, and pushes the element at the first/last element + * of the list stored at `destination` depending on `whereTo`. + * `BLMOVE` is the blocking variant of {@link lmove}. + * + * @see {@link https://valkey.io/commands/blmove/|valkey.io} for details. + * @remarks When in cluster mode, both `source` and `destination` must map to the same hash slot. + * @remarks `BLMOVE` is a client blocking command, see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands|Valkey Glide Wiki} for more details and best practices. + * @remarks Since Valkey version 6.2.0. + * + * @param source - The key to the source list. + * @param destination - The key to the destination list. + * @param whereFrom - The {@link ListDirection} to remove the element from. + * @param whereTo - The {@link ListDirection} to add the element to. + * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of `0` will block indefinitely. + * + * Command Response - The popped element, or `null` if `source` does not exist or if the operation timed-out. + */ + public blmove( + source: GlideString, + destination: GlideString, + whereFrom: ListDirection, + whereTo: ListDirection, + timeout: number, + ): T { + return this.addAndReturn( + createBLMove(source, destination, whereFrom, whereTo, timeout), + ); + } + /** * Sets the list element at `index` to `element`. * The index is zero-based, so `0` means the first element, `1` the second element and so on. * Negative indices can be used to designate elements starting at the tail of * the list. Here, `-1` means the last element, `-2` means the penultimate and so forth. * - * See https://valkey.io/commands/lset/ for details. + * @see {@link https://valkey.io/commands/lset/|valkey.io} for details. * * @param key - The key of the list. * @param index - The index of the element in the list to be set. @@ -600,7 +1200,7 @@ export class BaseTransaction> { * * Command Response - Always "OK". */ - public lset(key: string, index: number, element: string): T { + public lset(key: GlideString, index: number, element: GlideString): T { return this.addAndReturn(createLSet(key, index, element)); } @@ -608,7 +1208,7 @@ export class BaseTransaction> { * The offsets `start` and `end` are zero-based indexes, with 0 being the first element of the list, 1 being the next element and so on. * These offsets can also be negative numbers indicating offsets starting at the end of the list, * with -1 being the last element of the list, -2 being the penultimate, and so on. - * See https://valkey.io/commands/ltrim/ for details. + * @see {@link https://valkey.io/commands/ltrim/|valkey.io} for details. * * @param key - The key of the list. * @param start - The starting point of the range. @@ -619,7 +1219,7 @@ export class BaseTransaction> { * If `end` exceeds the actual end of the list, it will be treated like the last element of the list. * If `key` does not exist the command will be ignored. */ - public ltrim(key: string, start: number, end: number): T { + public ltrim(key: GlideString, start: number, end: number): T { return this.addAndReturn(createLTrim(key, start, end)); } @@ -635,39 +1235,54 @@ export class BaseTransaction> { * Command Response - the number of the removed elements. * If `key` does not exist, 0 is returned. */ - public lrem(key: string, count: number, element: string): T { + public lrem(key: GlideString, count: number, element: string): T { return this.addAndReturn(createLRem(key, count, element)); } /** Inserts all the specified values at the tail of the list stored at `key`. * `elements` are inserted one after the other to the tail of the list, from the leftmost element to the rightmost element. * If `key` does not exist, it is created as empty list before performing the push operations. - * See https://valkey.io/commands/rpush/ for details. + * @see {@link https://valkey.io/commands/rpush/|valkey.io} for details. * * @param key - The key of the list. * @param elements - The elements to insert at the tail of the list stored at `key`. * * Command Response - the length of the list after the push operations. */ - public rpush(key: string, elements: string[]): T { + public rpush(key: GlideString, elements: GlideString[]): T { return this.addAndReturn(createRPush(key, elements)); } + /** + * Inserts specified values at the tail of the `list`, only if `key` already + * exists and holds a list. + * + * @see {@link https://valkey.io/commands/rpushx/|valkey.io} for details. + * + * @param key - The key of the list. + * @param elements - The elements to insert at the tail of the list stored at `key`. + * + * Command Response - The length of the list after the push operation. + */ + public rpushx(key: GlideString, elements: GlideString[]): T { + return this.addAndReturn(createRPushX(key, elements)); + } + /** Removes and returns the last elements of the list stored at `key`. * The command pops a single element from the end of the list. - * See https://valkey.io/commands/rpop/ for details. + * @see {@link https://valkey.io/commands/rpop/|valkey.io} for details. * * @param key - The key of the list. * * Command Response - The value of the last element. * If `key` does not exist null will be returned. */ - public rpop(key: string): T { + public rpop(key: GlideString): T { return this.addAndReturn(createRPop(key)); } /** Removes and returns up to `count` elements from the list stored at `key`, depending on the list's length. - * See https://valkey.io/commands/rpop/ for details. + * @see {@link https://valkey.io/commands/rpop/|valkey.io} for details. * * @param key - The key of the list. * @param count - The count of the elements to pop from the list. @@ -675,25 +1290,25 @@ export class BaseTransaction> { * Command Response - A list of popped elements will be returned depending on the list's length. * If `key` does not exist null will be returned. */ - public rpopCount(key: string, count: number): T { + public rpopCount(key: GlideString, count: number): T { return this.addAndReturn(createRPop(key, count)); } /** Adds the specified members to the set stored at `key`. Specified members that are already a member of this set are ignored. * If `key` does not exist, a new set is created before adding `members`. - * See https://valkey.io/commands/sadd/ for details. + * @see {@link https://valkey.io/commands/sadd/|valkey.io} for details. * * @param key - The key to store the members to its set. * @param members - A list of members to add to the set stored at `key`. * * Command Response - the number of members that were added to the set, not including all the members already present in the set. */ - public sadd(key: string, members: string[]): T { + public sadd(key: GlideString, members: GlideString[]): T { return this.addAndReturn(createSAdd(key, members)); } /** Removes the specified members from the set stored at `key`. Specified members that are not a member of this set are ignored. - * See https://valkey.io/commands/srem/ for details. + * @see {@link https://valkey.io/commands/srem/|valkey.io} for details. * * @param key - The key to remove the members from its set. * @param members - A list of members to remove from the set stored at `key`. @@ -701,25 +1316,45 @@ export class BaseTransaction> { * Command Response - the number of members that were removed from the set, not including non existing members. * If `key` does not exist, it is treated as an empty set and this command returns 0. */ - public srem(key: string, members: string[]): T { + public srem(key: GlideString, members: GlideString[]): T { return this.addAndReturn(createSRem(key, members)); } + /** + * Iterates incrementally over a set. + * + * @see {@link https://valkey.io/commands/sscan} for details. + * + * @param key - The key of the set. + * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of the search. + * @param options - The (Optional) {@link BaseScanOptions}. + * + * Command Response - An array of the cursor and the subset of the set held by `key`. The first element is always the `cursor` and for the next iteration of results. + * The `cursor` will be `"0"` on the last iteration of the set. The second element is always an array of the subset of the set held in `key`. + */ + public sscan( + key: GlideString, + cursor: GlideString, + options?: BaseScanOptions, + ): T { + return this.addAndReturn(createSScan(key, cursor, options)); + } + /** Returns all the members of the set value stored at `key`. - * See https://valkey.io/commands/smembers/ for details. + * @see {@link https://valkey.io/commands/smembers/|valkey.io} for details. * * @param key - The key to return its members. * * Command Response - all members of the set. * If `key` does not exist, it is treated as an empty set and this command returns empty list. */ - public smembers(key: string): T { + public smembers(key: GlideString): T { return this.addAndReturn(createSMembers(key), true); } /** Moves `member` from the set at `source` to the set at `destination`, removing it from the source set. * Creates a new destination set if needed. The operation is atomic. - * See https://valkey.io/commands/smove for more details. + * @see {@link https://valkey.io/commands/smove/|valkey.io} for more details. * * @param source - The key of the set to remove the element from. * @param destination - The key of the set to add the element to. @@ -727,87 +1362,105 @@ export class BaseTransaction> { * * Command Response - `true` on success, or `false` if the `source` set does not exist or the element is not a member of the source set. */ - public smove(source: string, destination: string, member: string): T { + public smove( + source: GlideString, + destination: GlideString, + member: GlideString, + ): T { return this.addAndReturn(createSMove(source, destination, member)); } /** Returns the set cardinality (number of elements) of the set stored at `key`. - * See https://valkey.io/commands/scard/ for details. + * @see {@link https://valkey.io/commands/scard/|valkey.io} for details. * * @param key - The key to return the number of its members. * * Command Response - the cardinality (number of elements) of the set, or 0 if key does not exist. */ - public scard(key: string): T { + public scard(key: GlideString): T { return this.addAndReturn(createSCard(key)); } /** Gets the intersection of all the given sets. * When in cluster mode, all `keys` must map to the same hash slot. - * See https://valkey.io/docs/latest/commands/sinter/ for more details. + * @see {@link https://valkey.io/commands/sinter/|valkey.io} for details. * * @param keys - The `keys` of the sets to get the intersection. * * Command Response - A set of members which are present in all given sets. * If one or more sets do not exist, an empty set will be returned. */ - public sinter(keys: string[]): T { + public sinter(keys: GlideString[]): T { return this.addAndReturn(createSInter(keys), true); } + /** + * Gets the cardinality of the intersection of all the given sets. + * + * @see {@link https://valkey.io/commands/sintercard/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param keys - The keys of the sets. + * + * Command Response - The cardinality of the intersection result. If one or more sets do not exist, `0` is returned. + */ + public sintercard(keys: GlideString[], limit?: number): T { + return this.addAndReturn(createSInterCard(keys, limit)); + } + /** * Stores the members of the intersection of all given sets specified by `keys` into a new set at `destination`. * - * See https://valkey.io/commands/sinterstore/ for more details. + * @see {@link https://valkey.io/commands/sinterstore/|valkey.io} for details. * * @param destination - The key of the destination set. * @param keys - The keys from which to retrieve the set members. * * Command Response - The number of elements in the resulting set. */ - public sinterstore(destination: string, keys: string[]): T { + public sinterstore(destination: GlideString, keys: GlideString[]): T { return this.addAndReturn(createSInterStore(destination, keys)); } /** * Computes the difference between the first set and all the successive sets in `keys`. * - * See https://valkey.io/commands/sdiff/ for more details. + * @see {@link https://valkey.io/commands/sdiff/|valkey.io} for details. * * @param keys - The keys of the sets to diff. * * Command Response - A `Set` of elements representing the difference between the sets. * If a key in `keys` does not exist, it is treated as an empty set. */ - public sdiff(keys: string[]): T { + public sdiff(keys: GlideString[]): T { return this.addAndReturn(createSDiff(keys), true); } /** * Stores the difference between the first set and all the successive sets in `keys` into a new set at `destination`. * - * See https://valkey.io/commands/sdiffstore/ for more details. + * @see {@link https://valkey.io/commands/sdiffstore/|valkey.io} for details. * * @param destination - The key of the destination set. * @param keys - The keys of the sets to diff. * * Command Response - The number of elements in the resulting set. */ - public sdiffstore(destination: string, keys: string[]): T { + public sdiffstore(destination: GlideString, keys: GlideString[]): T { return this.addAndReturn(createSDiffStore(destination, keys)); } /** * Gets the union of all the given sets. * - * See https://valkey.io/commands/sunion/ for more details. + * @see {@link https://valkey.io/commands/sunion/|valkey.io} for details. * * @param keys - The keys of the sets. * * Command Response - A `Set` of members which are present in at least one of the given sets. * If none of the sets exist, an empty `Set` will be returned. */ - public sunion(keys: string[]): T { + public sunion(keys: GlideString[]): T { return this.addAndReturn(createSUnion(keys), true); } @@ -815,19 +1468,19 @@ export class BaseTransaction> { * Stores the members of the union of all given sets specified by `keys` into a new set * at `destination`. * - * See https://valkey.io/commands/sunionstore/ for details. + * @see {@link https://valkey.io/commands/sunionstore/|valkey.io} for details. * * @param destination - The key of the destination set. * @param keys - The keys from which to retrieve the set members. * * Command Response - The number of elements in the resulting set. */ - public sunionstore(destination: string, keys: string[]): T { + public sunionstore(destination: GlideString, keys: GlideString[]): T { return this.addAndReturn(createSUnionStore(destination, keys)); } /** Returns if `member` is a member of the set stored at `key`. - * See https://valkey.io/commands/sismember/ for more details. + * @see {@link https://valkey.io/commands/sismember/|valkey.io} for details. * * @param key - The key of the set. * @param member - The member to check for existence in the set. @@ -835,12 +1488,27 @@ export class BaseTransaction> { * Command Response - `true` if the member exists in the set, `false` otherwise. * If `key` doesn't exist, it is treated as an empty set and the command returns `false`. */ - public sismember(key: string, member: string): T { + public sismember(key: GlideString, member: GlideString): T { return this.addAndReturn(createSIsMember(key, member)); } + /** + * Checks whether each member is contained in the members of the set stored at `key`. + * + * @see {@link https://valkey.io/commands/smismember/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the set to check. + * @param members - A list of members to check for existence in the set. + * + * Command Response - An `array` of `boolean` values, each indicating if the respective member exists in the set. + */ + public smismember(key: GlideString, members: GlideString[]): T { + return this.addAndReturn(createSMIsMember(key, members)); + } + /** Removes and returns one random member from the set value store at `key`. - * See https://valkey.io/commands/spop/ for details. + * @see {@link https://valkey.io/commands/spop/|valkey.io} for details. * To pop multiple members, see `spopCount`. * * @param key - The key of the set. @@ -848,12 +1516,12 @@ export class BaseTransaction> { * Command Response - the value of the popped member. * If `key` does not exist, null will be returned. */ - public spop(key: string): T { + public spop(key: GlideString): T { return this.addAndReturn(createSPop(key)); } /** Removes and returns up to `count` random members from the set value store at `key`, depending on the set's length. - * See https://valkey.io/commands/spop/ for details. + * @see {@link https://valkey.io/commands/spop/|valkey.io} for details. * * @param key - The key of the set. * @param count - The count of the elements to pop from the set. @@ -861,109 +1529,165 @@ export class BaseTransaction> { * Command Response - A list of popped elements will be returned depending on the set's length. * If `key` does not exist, empty list will be returned. */ - public spopCount(key: string, count: number): T { + public spopCount(key: GlideString, count: number): T { return this.addAndReturn(createSPop(key, count), true); } - /** Returns the number of keys in `keys` that exist in the database. - * See https://valkey.io/commands/exists/ for details. + /** Returns a random element from the set value stored at `key`. + * + * @see {@link https://valkey.io/commands/srandmember/|valkey.io} for more details. + * + * @param key - The key from which to retrieve the set member. + * Command Response - A random element from the set, or null if `key` does not exist. + */ + public srandmember(key: GlideString): T { + return this.addAndReturn(createSRandMember(key)); + } + + /** Returns one or more random elements from the set value stored at `key`. + * + * @see {@link https://valkey.io/commands/srandmember/|valkey.io} for more details. + * + * @param key - The key of the sorted set. + * @param count - The number of members to return. + * If `count` is positive, returns unique members. + * If `count` is negative, allows for duplicates members. + * Command Response - A list of members from the set. If the set does not exist or is empty, an empty list will be returned. + */ + public srandmemberCount(key: GlideString, count: number): T { + return this.addAndReturn(createSRandMember(key, count)); + } + + /** + * Returns the number of keys in `keys` that exist in the database. + * + * @see {@link https://valkey.io/commands/exists/|valkey.io} for details. * * @param keys - The keys list to check. * * Command Response - the number of keys that exist. If the same existing key is mentioned in `keys` multiple times, - * it will be counted multiple times. + * it will be counted multiple times. */ - public exists(keys: string[]): T { + public exists(keys: GlideString[]): T { return this.addAndReturn(createExists(keys)); } - /** Removes the specified keys. A key is ignored if it does not exist. - * This command, similar to DEL, removes specified keys and ignores non-existent ones. - * However, this command does not block the server, while [DEL](https://valkey.io/commands/del) does. - * See https://valkey.io/commands/unlink/ for details. + /** + * Removes the specified keys. A key is ignored if it does not exist. + * This command, similar to {@link del}, removes specified keys and ignores non-existent ones. + * However, this command does not block the server, while {@link https://valkey.io/commands/del|`DEL`} does. + * + * @see {@link https://valkey.io/commands/unlink/|valkey.io} for details. * * @param keys - The keys we wanted to unlink. * - * Command Response - the number of keys that were unlinked. + * Command Response - The number of keys that were unlinked. */ - public unlink(keys: string[]): T { + public unlink(keys: GlideString[]): T { return this.addAndReturn(createUnlink(keys)); } - /** Sets a timeout on `key` in seconds. After the timeout has expired, the key will automatically be deleted. + /** + * Sets a timeout on `key` in seconds. After the timeout has expired, the key will automatically be deleted. * If `key` already has an existing expire set, the time to live is updated to the new value. * If `seconds` is non-positive number, the key will be deleted rather than expired. * The timeout will only be cleared by commands that delete or overwrite the contents of `key`. - * See https://valkey.io/commands/expire/ for details. + * + * @see {@link https://valkey.io/commands/expire/|valkey.io} for details. * * @param key - The key to set timeout on it. * @param seconds - The timeout in seconds. - * @param option - The expire option. + * @param option - (Optional) The expire option - see {@link ExpireOptions}. * * Command Response - `true` if the timeout was set. `false` if the timeout was not set. e.g. key doesn't exist, - * or operation skipped due to the provided arguments. + * or operation skipped due to the provided arguments. */ - public expire(key: string, seconds: number, option?: ExpireOptions): T { + public expire( + key: GlideString, + seconds: number, + option?: ExpireOptions, + ): T { return this.addAndReturn(createExpire(key, seconds, option)); } - /** Sets a timeout on `key`. It takes an absolute Unix timestamp (seconds since January 1, 1970) instead of specifying the number of seconds. + /** + * Sets a timeout on `key`. It takes an absolute Unix timestamp (seconds since January 1, 1970) instead of specifying the number of seconds. * A timestamp in the past will delete the key immediately. After the timeout has expired, the key will automatically be deleted. * If `key` already has an existing expire set, the time to live is updated to the new value. * The timeout will only be cleared by commands that delete or overwrite the contents of `key`. - * See https://valkey.io/commands/expireat/ for details. + * + * @see {@link https://valkey.io/commands/expireat/|valkey.io} for details. * * @param key - The key to set timeout on it. * @param unixSeconds - The timeout in an absolute Unix timestamp. - * @param option - The expire option. + * @param option - (Optional) The expire option - see {@link ExpireOptions}. * * Command Response - `true` if the timeout was set. `false` if the timeout was not set. e.g. key doesn't exist, - * or operation skipped due to the provided arguments. + * or operation skipped due to the provided arguments. */ public expireAt( - key: string, + key: GlideString, unixSeconds: number, option?: ExpireOptions, ): T { return this.addAndReturn(createExpireAt(key, unixSeconds, option)); } - /** Sets a timeout on `key` in milliseconds. After the timeout has expired, the key will automatically be deleted. + /** + * Returns the absolute Unix timestamp (since January 1, 1970) at which the given `key` will expire, in seconds. + * To get the expiration with millisecond precision, use {@link pexpiretime}. + * + * @see {@link https://valkey.io/commands/expiretime/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param key - The `key` to determine the expiration value of. + * + * Command Response - The expiration Unix timestamp in seconds, `-2` if `key` does not exist or `-1` if `key` exists but has no associated expire. + */ + public expireTime(key: GlideString): T { + return this.addAndReturn(createExpireTime(key)); + } + + /** + * Sets a timeout on `key` in milliseconds. After the timeout has expired, the key will automatically be deleted. * If `key` already has an existing expire set, the time to live is updated to the new value. * If `milliseconds` is non-positive number, the key will be deleted rather than expired. * The timeout will only be cleared by commands that delete or overwrite the contents of `key`. - * See https://valkey.io/commands/pexpire/ for details. + * + * @see {@link https://valkey.io/commands/pexpire/|valkey.io} for details. * * @param key - The key to set timeout on it. * @param milliseconds - The timeout in milliseconds. - * @param option - The expire option. + * @param option - (Optional) The expire option - see {@link ExpireOptions}. * * Command Response - `true` if the timeout was set. `false` if the timeout was not set. e.g. key doesn't exist, - * or operation skipped due to the provided arguments. + * or operation skipped due to the provided arguments. */ public pexpire( - key: string, + key: GlideString, milliseconds: number, option?: ExpireOptions, ): T { return this.addAndReturn(createPExpire(key, milliseconds, option)); } - /** Sets a timeout on `key`. It takes an absolute Unix timestamp (milliseconds since January 1, 1970) instead of specifying the number of milliseconds. + /** + * Sets a timeout on `key`. It takes an absolute Unix timestamp (milliseconds since January 1, 1970) instead of specifying the number of milliseconds. * A timestamp in the past will delete the key immediately. After the timeout has expired, the key will automatically be deleted. * If `key` already has an existing expire set, the time to live is updated to the new value. * The timeout will only be cleared by commands that delete or overwrite the contents of `key`. - * See https://valkey.io/commands/pexpireat/ for details. + * + * @see {@link https://valkey.io/commands/pexpireat/|valkey.io} for details. * * @param key - The key to set timeout on it. * @param unixMilliseconds - The timeout in an absolute Unix timestamp. - * @param option - The expire option. + * @param option - (Optional) The expire option - see {@link ExpireOptions}. * * Command Response - `true` if the timeout was set. `false` if the timeout was not set. e.g. key doesn't exist, - * or operation skipped due to the provided arguments. + * or operation skipped due to the provided arguments. */ public pexpireAt( - key: string, + key: GlideString, unixMilliseconds: number, option?: ExpireOptions, ): T { @@ -972,54 +1696,64 @@ export class BaseTransaction> { ); } - /** Returns the remaining time to live of `key` that has a timeout. - * See https://valkey.io/commands/ttl/ for details. + /** + * Returns the absolute Unix timestamp (since January 1, 1970) at which the given `key` will expire, in milliseconds. + * + * @see {@link https://valkey.io/commands/pexpiretime/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param key - The `key` to determine the expiration value of. + * + * Command Response - The expiration Unix timestamp in seconds, `-2` if `key` does not exist or `-1` if `key` exists but has no associated expire. + */ + public pexpireTime(key: GlideString): T { + return this.addAndReturn(createPExpireTime(key)); + } + + /** + * Returns the remaining time to live of `key` that has a timeout. + * + * @see {@link https://valkey.io/commands/ttl/|valkey.io} for details. * * @param key - The key to return its timeout. * - * Command Response - TTL in seconds, -2 if `key` does not exist or -1 if `key` exists but has no associated expire. + * Command Response - TTL in seconds, `-2` if `key` does not exist or `-1` if `key` exists but has no associated expire. */ - public ttl(key: string): T { + public ttl(key: GlideString): T { return this.addAndReturn(createTTL(key)); } - /** Adds members with their scores to the sorted set stored at `key`. + /** + * Adds members with their scores to the sorted set stored at `key`. * If a member is already a part of the sorted set, its score is updated. - * See https://valkey.io/commands/zadd/ for more details. + * + * @see {@link https://valkey.io/commands/zadd/|valkey.io} for details. * * @param key - The key of the sorted set. * @param membersScoresMap - A mapping of members to their corresponding scores. - * @param options - The ZAdd options. - * @param changed - Modify the return value from the number of new elements added, to the total number of elements changed. + * @param options - (Optional) The ZAdd options - see {@link ZAddOptions}. * * Command Response - The number of elements added to the sorted set. - * If `changed` is set, returns the number of elements updated in the sorted set. + * If {@link ZAddOptions.changed|changed} is set, returns the number of elements updated in the sorted set. */ public zadd( key: string, membersScoresMap: Record, options?: ZAddOptions, - changed?: boolean, ): T { - return this.addAndReturn( - createZAdd( - key, - membersScoresMap, - options, - changed ? "CH" : undefined, - ), - ); + return this.addAndReturn(createZAdd(key, membersScoresMap, options)); } - /** Increments the score of member in the sorted set stored at `key` by `increment`. + /** + * Increments the score of member in the sorted set stored at `key` by `increment`. * If `member` does not exist in the sorted set, it is added with `increment` as its score (as if its previous score was 0.0). * If `key` does not exist, a new sorted set with the specified member as its sole member is created. - * See https://valkey.io/commands/zadd/ for more details. + * @see {@link https://valkey.io/commands/zadd/|valkey.io} for details. * * @param key - The key of the sorted set. * @param member - A member in the sorted set to increment. * @param increment - The score to increment the member. - * @param options - The ZAdd options. + * @param options - (Optional) The ZAdd options - see {@link ZAddOptions}. * * Command Response - The score of the member. * If there was a conflict with the options, the operation aborts and null is returned. @@ -1031,13 +1765,13 @@ export class BaseTransaction> { options?: ZAddOptions, ): T { return this.addAndReturn( - createZAdd(key, { [member]: increment }, options, "INCR"), + createZAdd(key, { [member]: increment }, options, true), ); } /** Removes the specified members from the sorted set stored at `key`. * Specified members that are not a member of this set are ignored. - * See https://valkey.io/commands/zrem/ for more details. + * @see {@link https://valkey.io/commands/zrem/|valkey.io} for details. * * @param key - The key of the sorted set. * @param members - A list of members to remove from the sorted set. @@ -1049,37 +1783,87 @@ export class BaseTransaction> { return this.addAndReturn(createZRem(key, members)); } - /** Returns the cardinality (number of elements) of the sorted set stored at `key`. - * See https://valkey.io/commands/zcard/ for more details. + /** + * Returns the cardinality (number of elements) of the sorted set stored at `key`. + * + * @see {@link https://valkey.io/commands/zcard/|valkey.io} for details. * * @param key - The key of the sorted set. * * Command Response - The number of elements in the sorted set. - * If `key` does not exist, it is treated as an empty sorted set, and this command returns 0. + * If `key` does not exist, it is treated as an empty sorted set, and this command returns `0`. */ - public zcard(key: string): T { + public zcard(key: GlideString): T { return this.addAndReturn(createZCard(key)); } /** * Returns the cardinality of the intersection of the sorted sets specified by `keys`. * - * See https://valkey.io/commands/zintercard/ for more details. + * @see {@link https://valkey.io/commands/zintercard/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. * * @param keys - The keys of the sorted sets to intersect. * @param limit - An optional argument that can be used to specify a maximum number for the * intersection cardinality. If limit is not supplied, or if it is set to `0`, there will be no limit. * * Command Response - The cardinality of the intersection of the given sorted sets. - * - * since - Redis version 7.0.0. */ - public zintercard(keys: string[], limit?: number): T { + public zintercard(keys: GlideString[], limit?: number): T { return this.addAndReturn(createZInterCard(keys, limit)); } + /** + * Returns the difference between the first sorted set and all the successive sorted sets. + * To get the elements with their scores, see {@link zdiffWithScores}. + * + * @see {@link https://valkey.io/commands/zdiff/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param keys - The keys of the sorted sets. + * + * Command Response - An `array` of elements representing the difference between the sorted sets. + * If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`. + */ + public zdiff(keys: GlideString[]): T { + return this.addAndReturn(createZDiff(keys)); + } + + /** + * Returns the difference between the first sorted set and all the successive sorted sets, with the associated + * scores. + * + * @see {@link https://valkey.io/commands/zdiff/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param keys - The keys of the sorted sets. + * + * Command Response - A map of elements and their scores representing the difference between the sorted sets. + * If the first key does not exist, it is treated as an empty sorted set, and the command returns an empty `array`. + */ + public zdiffWithScores(keys: GlideString[]): T { + return this.addAndReturn(createZDiffWithScores(keys)); + } + + /** + * Calculates the difference between the first sorted set and all the successive sorted sets in `keys` and stores + * the difference as a sorted set to `destination`, overwriting it if it already exists. Non-existent keys are + * treated as empty sets. + * + * @see {@link https://valkey.io/commands/zdiffstore/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param destination - The key for the resulting sorted set. + * @param keys - The keys of the sorted sets to compare. + * + * Command Response - The number of members in the resulting sorted set stored at `destination`. + */ + public zdiffstore(destination: GlideString, keys: GlideString[]): T { + return this.addAndReturn(createZDiffStore(destination, keys)); + } + /** Returns the score of `member` in the sorted set stored at `key`. - * See https://valkey.io/commands/zscore/ for more details. + * @see {@link https://valkey.io/commands/zscore/|valkey.io} for details. * * @param key - The key of the sorted set. * @param member - The member whose score is to be retrieved. @@ -1092,21 +1876,63 @@ export class BaseTransaction> { return this.addAndReturn(createZScore(key, member)); } - /** Returns the number of members in the sorted set stored at `key` with scores between `minScore` and `maxScore`. - * See https://valkey.io/commands/zcount/ for more details. + /** + * Computes the union of sorted sets given by the specified `keys` and stores the result in `destination`. + * If `destination` already exists, it is overwritten. Otherwise, a new sorted set will be created. + * To get the result directly, see {@link zunionWithScores}. + * + * @see {@link https://valkey.io/commands/zunionstore/|valkey.io} for details. + * @param destination - The key of the destination sorted set. + * @param keys - The keys of the sorted sets with possible formats: + * string[] - for keys only. + * KeyWeight[] - for weighted keys with score multipliers. + * @param aggregationType - Specifies the aggregation strategy to apply when combining the scores of elements. See {@link AggregationType}. + * + * Command Response - The number of elements in the resulting sorted set stored at `destination`. + */ + public zunionstore( + destination: string, + keys: string[] | KeyWeight[], + aggregationType?: AggregationType, + ): T { + return this.addAndReturn( + createZUnionStore(destination, keys, aggregationType), + ); + } + + /** + * Returns the scores associated with the specified `members` in the sorted set stored at `key`. + * + * @see {@link https://valkey.io/commands/zmscore/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the sorted set. + * @param members - A list of members in the sorted set. + * + * Command Response - An `array` of scores corresponding to `members`. + * If a member does not exist in the sorted set, the corresponding value in the list will be `null`. + */ + public zmscore(key: string, members: string[]): T { + return this.addAndReturn(createZMScore(key, members)); + } + + /** + * Returns the number of members in the sorted set stored at `key` with scores between `minScore` and `maxScore`. + * + * @see {@link https://valkey.io/commands/zcount/|valkey.io} for details. * * @param key - The key of the sorted set. * @param minScore - The minimum score to count from. Can be positive/negative infinity, or specific score and inclusivity. * @param maxScore - The maximum score to count up to. Can be positive/negative infinity, or specific score and inclusivity. * * Command Response - The number of members in the specified score range. - * If `key` does not exist, it is treated as an empty sorted set, and the command returns 0. - * If `minScore` is greater than `maxScore`, 0 is returned. + * If `key` does not exist, it is treated as an empty sorted set, and the command returns `0`. + * If `minScore` is greater than `maxScore`, `0` is returned. */ public zcount( - key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + key: GlideString, + minScore: Boundary, + maxScore: Boundary, ): T { return this.addAndReturn(createZCount(key, minScore, maxScore)); } @@ -1114,15 +1940,15 @@ export class BaseTransaction> { /** Returns the specified range of elements in the sorted set stored at `key`. * ZRANGE can perform different types of range queries: by index (rank), by the score, or by lexicographical order. * - * See https://valkey.io/commands/zrange/ for more details. + * @see {@link https://valkey.io/commands/zrange/|valkey.io} for details. * To get the elements with their scores, see `zrangeWithScores`. * * @param key - The key of the sorted set. * @param rangeQuery - The range query object representing the type of range query to perform. - * For range queries by index (rank), use RangeByIndex. - * For range queries by lexicographical order, use RangeByLex. - * For range queries by score, use RangeByScore. - * @param reverse - If true, reverses the sorted set, with index 0 as the element with the highest score. + * - For range queries by index (rank), use {@link RangeByIndex}. + * - For range queries by lexicographical order, use {@link RangeByLex}. + * - For range queries by score, use {@link RangeByScore}. + * @param reverse - If `true`, reverses the sorted set, with index `0` as the element with the highest score. * * Command Response - A list of elements within the specified range. * If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty array. @@ -1137,14 +1963,14 @@ export class BaseTransaction> { /** Returns the specified range of elements with their scores in the sorted set stored at `key`. * Similar to ZRANGE but with a WITHSCORE flag. - * See https://valkey.io/commands/zrange/ for more details. + * @see {@link https://valkey.io/commands/zrange/|valkey.io} for details. * * @param key - The key of the sorted set. * @param rangeQuery - The range query object representing the type of range query to perform. - * For range queries by index (rank), use RangeByIndex. - * For range queries by lexicographical order, use RangeByLex. - * For range queries by score, use RangeByScore. - * @param reverse - If true, reverses the sorted set, with index 0 as the element with the highest score. + * - For range queries by index (rank), use {@link RangeByIndex}. + * - For range queries by lexicographical order, use {@link RangeByLex}. + * - For range queries by score, use {@link RangeByScore}. + * @param reverse - If `true`, reverses the sorted set, with index `0` as the element with the highest score. * * Command Response - A map of elements and their scores within the specified range. * If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty map. @@ -1159,24 +1985,55 @@ export class BaseTransaction> { ); } + /** + * Stores a specified range of elements from the sorted set at `source`, into a new + * sorted set at `destination`. If `destination` doesn't exist, a new sorted + * set is created; if it exists, it's overwritten. + * + * @see {@link https://valkey.io/commands/zrangestore/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param destination - The key for the destination sorted set. + * @param source - The key of the source sorted set. + * @param rangeQuery - The range query object representing the type of range query to perform. + * - For range queries by index (rank), use {@link RangeByIndex}. + * - For range queries by lexicographical order, use {@link RangeByLex}. + * - For range queries by score, use {@link RangeByScore}. + * @param reverse - If `true`, reverses the sorted set, with index `0` as the element with the highest score. + * + * Command Response - The number of elements in the resulting sorted set. + */ + public zrangeStore( + destination: string, + source: string, + rangeQuery: RangeByScore | RangeByLex | RangeByIndex, + reverse: boolean = false, + ): T { + return this.addAndReturn( + createZRangeStore(destination, source, rangeQuery, reverse), + ); + } + /** * Computes the intersection of sorted sets given by the specified `keys` and stores the result in `destination`. * If `destination` already exists, it is overwritten. Otherwise, a new sorted set will be created. * - * When in cluster mode, `destination` and all keys in `keys` must map to the same hash slot. + * @see {@link https://valkey.io/commands/zinterstore/|valkey.io} for details. * - * See https://valkey.io/commands/zinterstore/ for more details. + * @remarks Since Valkey version 6.2.0. * * @param destination - The key of the destination sorted set. * @param keys - The keys of the sorted sets with possible formats: - * string[] - for keys only. - * KeyWeight[] - for weighted keys with score multipliers. - * @param aggregationType - Specifies the aggregation strategy to apply when combining the scores of elements. See `AggregationType`. + * - `GlideString[]` - for keys only. + * - `KeyWeight[]` - for weighted keys with score multipliers. + * @param aggregationType - (Optional) Specifies the aggregation strategy to apply when combining the scores of elements. See {@link AggregationType}. + * If `aggregationType` is not specified, defaults to `AggregationType.SUM`. + * * Command Response - The number of elements in the resulting sorted set stored at `destination`. */ public zinterstore( - destination: string, - keys: string[] | KeyWeight[], + destination: GlideString, + keys: GlideString[] | KeyWeight[], aggregationType?: AggregationType, ): T { return this.addAndReturn( @@ -1184,33 +2041,163 @@ export class BaseTransaction> { ); } - /** Returns the string representation of the type of the value stored at `key`. - * See https://valkey.io/commands/type/ for more details. + /** + * Computes the intersection of sorted sets given by the specified `keys` and returns a list of intersecting elements. + * To get the scores as well, see {@link zinterWithScores}. + * To store the result in a key as a sorted set, see {@link zinterStore}. + * + * @remarks Since Valkey version 6.2.0. + * + * @see {@link https://valkey.io/commands/zinter/|valkey.io} for details. + * + * @param keys - The keys of the sorted sets. + * + * Command Response - The resulting array of intersecting elements. + */ + public zinter(keys: GlideString[]): T { + return this.addAndReturn(createZInter(keys)); + } + + /** + * Computes the intersection of sorted sets given by the specified `keys` and returns a list of intersecting elements with scores. + * To get the elements only, see {@link zinter}. + * To store the result in a key as a sorted set, see {@link zinterStore}. + * + * @see {@link https://valkey.io/commands/zinter/|valkey.io} for details. + * + * @remarks Since Valkey version 6.2.0. + * + * @param keys - The keys of the sorted sets with possible formats: + * - `GlideString[]` - for keys only. + * - `KeyWeight[]` - for weighted keys with score multipliers. + * @param aggregationType - (Optional) Specifies the aggregation strategy to apply when combining the scores of elements. See {@link AggregationType}. + * If `aggregationType` is not specified, defaults to `AggregationType.SUM`. + * + * Command Response - The resulting sorted set with scores. + */ + public zinterWithScores( + keys: GlideString[] | KeyWeight[], + aggregationType?: AggregationType, + ): T { + return this.addAndReturn(createZInter(keys, aggregationType, true)); + } + + /** + * Computes the union of sorted sets given by the specified `keys` and returns a list of union elements. + * To get the scores as well, see {@link zunionWithScores}. + * + * To store the result in a key as a sorted set, see {@link zunionStore}. + * + * @remarks Since Valkey version 6.2.0. + * + * @see {@link https://valkey.io/commands/zunion/|valkey.io} for details. + * + * @param keys - The keys of the sorted sets. + * + * Command Response - The resulting array of union elements. + */ + public zunion(keys: string[]): T { + return this.addAndReturn(createZUnion(keys)); + } + + /** + * Computes the intersection of sorted sets given by the specified `keys` and returns a list of union elements with scores. + * To get the elements only, see {@link zunion}. + * + * @see {@link https://valkey.io/commands/zunion/|valkey.io} for details. + * + * @remarks Since Valkey version 6.2.0. + * + * @param keys - The keys of the sorted sets with possible formats: + * - string[] - for keys only. + * - KeyWeight[] - for weighted keys with score multipliers. + * @param aggregationType - (Optional) Specifies the aggregation strategy to apply when combining the scores of elements. See {@link AggregationType}. + * If `aggregationType` is not specified, defaults to `AggregationType.SUM`. + * + * Command Response - The resulting sorted set with scores. + */ + public zunionWithScores( + keys: string[] | KeyWeight[], + aggregationType?: AggregationType, + ): T { + return this.addAndReturn(createZUnion(keys, aggregationType, true)); + } + + /** + * Returns a random member from the sorted set stored at `key`. + * + * @see {@link https://valkey.io/commands/zrandmember/|valkey.io} for details. + * + * @param keys - The key of the sorted set. + * Command Response - A string representing a random member from the sorted set. + * If the sorted set does not exist or is empty, the response will be `null`. + */ + public zrandmember(key: string): T { + return this.addAndReturn(createZRandMember(key)); + } + + /** + * Returns random members from the sorted set stored at `key`. + * + * @see {@link https://valkey.io/commands/zrandmember/|valkey.io} for details. + * + * @param keys - The key of the sorted set. + * @param count - The number of members to return. + * If `count` is positive, returns unique members. + * If negative, allows for duplicates. + * Command Response - An `array` of members from the sorted set. + * If the sorted set does not exist or is empty, the response will be an empty `array`. + */ + public zrandmemberWithCount(key: string, count: number): T { + return this.addAndReturn(createZRandMember(key, count)); + } + + /** + * Returns random members with scores from the sorted set stored at `key`. + * + * @see {@link https://valkey.io/commands/zrandmember/|valkey.io} for details. + * + * @param keys - The key of the sorted set. + * @param count - The number of members to return. + * If `count` is positive, returns unique members. + * If negative, allows for duplicates. + * Command Response - A 2D `array` of `[member, score]` `arrays`, where + * member is a `string` and score is a `number`. + * If the sorted set does not exist or is empty, the response will be an empty `array`. + */ + public zrandmemberWithCountWithScores(key: string, count: number): T { + return this.addAndReturn(createZRandMember(key, count, true)); + } + + /** + * Returns the string representation of the type of the value stored at `key`. + * + * @see {@link https://valkey.io/commands/type/|valkey.io} for details. * * @param key - The key to check its data type. * * Command Response - If the key exists, the type of the stored value is returned. Otherwise, a "none" string is returned. */ - public type(key: string): T { + public type(key: GlideString): T { return this.addAndReturn(createType(key)); } /** Returns the length of the string value stored at `key`. - * See https://valkey.io/commands/strlen/ for more details. + * @see {@link https://valkey.io/commands/strlen/|valkey.io} for details. * * @param key - The `key` to check its length. * * Command Response - The length of the string value stored at `key` * If `key` does not exist, it is treated as an empty string, and the command returns 0. */ - public strlen(key: string): T { + public strlen(key: GlideString): T { return this.addAndReturn(createStrlen(key)); } /** Removes and returns the members with the lowest scores from the sorted set stored at `key`. * If `count` is provided, up to `count` members with the lowest scores are removed and returned. * Otherwise, only one member with the lowest score is removed and returned. - * See https://valkey.io/commands/zpopmin for more details. + * @see {@link https://valkey.io/commands/zpopmin/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param count - Specifies the quantity of members to pop. If not specified, pops one member. @@ -1223,10 +2210,29 @@ export class BaseTransaction> { return this.addAndReturn(createZPopMin(key, count)); } + /** + * Blocks the connection until it removes and returns a member with the lowest score from the + * first non-empty sorted set, with the given `key` being checked in the order they + * are provided. + * `BZPOPMIN` is the blocking variant of {@link zpopmin}. + * + * @see {@link https://valkey.io/commands/bzpopmin/|valkey.io} for details. + * + * @param keys - The keys of the sorted sets. + * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of + * `0` will block indefinitely. Since Valkey version 6.0.0: timeout is interpreted as a double instead of an integer. + * + * Command Response - An `array` containing the key where the member was popped out, the member, itself, and the member score. + * If no member could be popped and the `timeout` expired, returns `null`. + */ + public bzpopmin(keys: GlideString[], timeout: number): T { + return this.addAndReturn(createBZPopMin(keys, timeout)); + } + /** Removes and returns the members with the highest scores from the sorted set stored at `key`. * If `count` is provided, up to `count` members with the highest scores are removed and returned. * Otherwise, only one member with the highest score is removed and returned. - * See https://valkey.io/commands/zpopmax for more details. + * @see {@link https://valkey.io/commands/zpopmax/|valkey.io} for more details. * * @param key - The key of the sorted set. * @param count - Specifies the quantity of members to pop. If not specified, pops one member. @@ -1239,32 +2245,55 @@ export class BaseTransaction> { return this.addAndReturn(createZPopMax(key, count)); } - /** Echoes the provided `message` back. - * See https://valkey.io/commands/echo for more details. + /** + * Blocks the connection until it removes and returns a member with the highest score from the + * first non-empty sorted set, with the given `key` being checked in the order they + * are provided. + * `BZPOPMAX` is the blocking variant of {@link zpopmax}. + * + * @see {@link https://valkey.io/commands/bzpopmax/|valkey.io} for details. + * + * @param keys - The keys of the sorted sets. + * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of + * `0` will block indefinitely. Since 6.0.0: timeout is interpreted as a double instead of an integer. + * + * Command Response - An `array` containing the key where the member was popped out, the member, itself, and the member score. + * If no member could be popped and the `timeout` expired, returns `null`. + */ + public bzpopmax(keys: GlideString[], timeout: number): T { + return this.addAndReturn(createBZPopMax(keys, timeout)); + } + + /** + * Echoes the provided `message` back + * + * @see {@link https://valkey.io/commands/echo/|valkey.io} for more details. * * @param message - The message to be echoed back. * * Command Response - The provided `message`. */ - public echo(message: string): T { + public echo(message: GlideString): T { return this.addAndReturn(createEcho(message)); } - /** Returns the remaining time to live of `key` that has a timeout, in milliseconds. - * See https://valkey.io/commands/pttl for more details. + /** + * Returns the remaining time to live of `key` that has a timeout, in milliseconds. + * + * @see {@link https://valkey.io/commands/pttl/|valkey.io} for more details. * * @param key - The key to return its timeout. * - * Command Response - TTL in milliseconds. -2 if `key` does not exist, -1 if `key` exists but has no associated expire. + * Command Response - TTL in milliseconds, `-2` if `key` does not exist, `-1` if `key` exists but has no associated expire. */ - public pttl(key: string): T { + public pttl(key: GlideString): T { return this.addAndReturn(createPTTL(key)); } /** Removes all elements in the sorted set stored at `key` with rank between `start` and `end`. * Both `start` and `end` are zero-based indexes with 0 being the element with the lowest score. * These indexes can be negative numbers, where they indicate offsets starting at the element with the highest score. - * See https://valkey.io/commands/zremrangebyrank/ for more details. + * @see {@link https://valkey.io/commands/zremrangebyrank/|valkey.io} for details. * * @param key - The key of the sorted set. * @param start - The starting point of the range. @@ -1279,8 +2308,29 @@ export class BaseTransaction> { return this.addAndReturn(createZRemRangeByRank(key, start, end)); } + /** + * Removes all elements in the sorted set stored at `key` with lexicographical order between `minLex` and `maxLex`. + * + * @see {@link https://valkey.io/commands/zremrangebylex/|valkey.io} for details. + * + * @param key - The key of the sorted set. + * @param minLex - The minimum lex to count from. Can be positive/negative infinity, or a specific lex and inclusivity. + * @param maxLex - The maximum lex to count up to. Can be positive/negative infinity, or a specific lex and inclusivity. + * + * Command Response - The number of members removed. + * If `key` does not exist, it is treated as an empty sorted set, and the command returns 0. + * If `minLex` is greater than `maxLex`, 0 is returned. + */ + public zremRangeByLex( + key: string, + minLex: Boundary, + maxLex: Boundary, + ): T { + return this.addAndReturn(createZRemRangeByLex(key, minLex, maxLex)); + } + /** Removes all elements in the sorted set stored at `key` with a score between `minScore` and `maxScore`. - * See https://valkey.io/commands/zremrangebyscore/ for more details. + * @see {@link https://valkey.io/commands/zremrangebyscore/|valkey.io} for details. * * @param key - The key of the sorted set. * @param minScore - The minimum score to remove from. Can be positive/negative infinity, or specific score and inclusivity. @@ -1292,16 +2342,37 @@ export class BaseTransaction> { */ public zremRangeByScore( key: string, - minScore: ScoreBoundary, - maxScore: ScoreBoundary, + minScore: Boundary, + maxScore: Boundary, ): T { return this.addAndReturn( createZRemRangeByScore(key, minScore, maxScore), ); } + /** + * Returns the number of members in the sorted set stored at 'key' with scores between 'minLex' and 'maxLex'. + * + * @see {@link https://valkey.io/commands/zlexcount/|valkey.io} for details. + * + * @param key - The key of the sorted set. + * @param minLex - The minimum lex to count from. Can be positive/negative infinity, or a specific lex and inclusivity. + * @param maxLex - The maximum lex to count up to. Can be positive/negative infinity, or a specific lex and inclusivity. + * + * Command Response - The number of members in the specified lex range. + * If 'key' does not exist, it is treated as an empty sorted set, and the command returns '0'. + * If maxLex is less than minLex, '0' is returned. + */ + public zlexcount( + key: string, + minLex: Boundary, + maxLex: Boundary, + ): T { + return this.addAndReturn(createZLexCount(key, minLex, maxLex)); + } + /** Returns the rank of `member` in the sorted set stored at `key`, with scores ordered from low to high. - * See https://valkey.io/commands/zrank for more details. + * @see {@link https://valkey.io/commands/zrank/|valkey.io} for more details. * To get the rank of `member` with its score, see `zrankWithScore`. * * @param key - The key of the sorted set. @@ -1315,41 +2386,77 @@ export class BaseTransaction> { } /** Returns the rank of `member` in the sorted set stored at `key` with its score, where scores are ordered from the lowest to highest. - * See https://valkey.io/commands/zrank for more details. + * + * @see {@link https://valkey.io/commands/zrank/|valkey.io} for more details. + * @remarks Since Valkey version 7.2.0. * * @param key - The key of the sorted set. * @param member - The member whose rank is to be retrieved. * * Command Response - A list containing the rank and score of `member` in the sorted set. * If `key` doesn't exist, or if `member` is not present in the set, null will be returned. - * - * since - Redis version 7.2.0. */ public zrankWithScore(key: string, member: string): T { return this.addAndReturn(createZRank(key, member, true)); } - /** Remove the existing timeout on `key`, turning the key from volatile (a key with an expire set) to + /** + * Returns the rank of `member` in the sorted set stored at `key`, where + * scores are ordered from the highest to lowest, starting from 0. + * To get the rank of `member` with its score, see {@link zrevrankWithScore}. + * + * @see {@link https://valkey.io/commands/zrevrank/|valkey.io} for details. + * + * @param key - The key of the sorted set. + * @param member - The member whose rank is to be retrieved. + * + * Command Response - The rank of `member` in the sorted set, where ranks are ordered from high to low based on scores. + * If `key` doesn't exist, or if `member` is not present in the set, `null` will be returned. + */ + public zrevrank(key: string, member: string): T { + return this.addAndReturn(createZRevRank(key, member)); + } + + /** + * Returns the rank of `member` in the sorted set stored at `key` with its + * score, where scores are ordered from the highest to lowest, starting from 0. + * + * @see {@link https://valkey.io/commands/zrevrank/|valkey.io} for details. + * @remarks Since Valkey version 7.2.0. + * + * @param key - The key of the sorted set. + * @param member - The member whose rank is to be retrieved. + * + * Command Response - A list containing the rank and score of `member` in the sorted set, where ranks + * are ordered from high to low based on scores. + * If `key` doesn't exist, or if `member` is not present in the set, `null` will be returned. + */ + public zrevrankWithScore(key: string, member: string): T { + return this.addAndReturn(createZRevRankWithScore(key, member)); + } + + /** + * Removes the existing timeout on `key`, turning the key from volatile (a key with an expire set) to * persistent (a key that will never expire as no timeout is associated). - * See https://valkey.io/commands/persist/ for more details. + * + * @see {@link https://valkey.io/commands/persist/|valkey.io} for details. * * @param key - The key to remove the existing timeout on. * * Command Response - `false` if `key` does not exist or does not have an associated timeout, `true` if the timeout has been removed. */ - public persist(key: string): T { + public persist(key: GlideString): T { return this.addAndReturn(createPersist(key)); } /** Executes a single command, without checking inputs. Every part of the command, including subcommands, * should be added as a separate value in args. * - * See the [Glide for Redis Wiki](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command) - * for details on the restrictions and limitations of the custom command API. + * @see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command|Valkey Glide Wiki} for details on the restrictions and limitations of the custom command API. * * Command Response - A response from Redis with an `Object`. */ - public customCommand(args: string[]): T { + public customCommand(args: GlideString[]): T { return this.addAndReturn(createCustomCommand(args)); } @@ -1357,21 +2464,21 @@ export class BaseTransaction> { * The index is zero-based, so 0 means the first element, 1 the second element and so on. * Negative indices can be used to designate elements starting at the tail of the list. * Here, -1 means the last element, -2 means the penultimate and so forth. - * See https://valkey.io/commands/lindex/ for more details. + * @see {@link https://valkey.io/commands/lindex/|valkey.io} for details. * * @param key - The `key` of the list. * @param index - The `index` of the element in the list to retrieve. * Command Response - The element at index in the list stored at `key`. * If `index` is out of range or if `key` does not exist, null is returned. */ - public lindex(key: string, index: number): T { + public lindex(key: GlideString, index: number): T { return this.addAndReturn(createLIndex(key, index)); } /** * Inserts `element` in the list at `key` either before or after the `pivot`. * - * See https://valkey.io/commands/linsert/ for more details. + * @see {@link https://valkey.io/commands/linsert/|valkey.io} for details. * * @param key - The key of the list. * @param position - The relative position to insert into - either `InsertPosition.Before` or @@ -1384,108 +2491,548 @@ export class BaseTransaction> { * If the `pivot` wasn't found, returns `0`. */ public linsert( - key: string, + key: GlideString, position: InsertPosition, - pivot: string, - element: string, + pivot: GlideString, + element: GlideString, ): T { return this.addAndReturn(createLInsert(key, position, pivot, element)); } /** * Adds an entry to the specified stream stored at `key`. If the `key` doesn't exist, the stream is created. - * See https://valkey.io/commands/xadd/ for more details. + * @see {@link https://valkey.io/commands/xadd/|valkey.io} for details. * * @param key - The key of the stream. * @param values - field-value pairs to be added to the entry. - * @returns The id of the added entry, or `null` if `options.makeStream` is set to `false` and no stream with the matching `key` exists. + * @param options - (Optional) Stream add options. + * + * Command Response - The id of the added entry, or `null` if `options.makeStream` is set to `false` and no stream with the matching `key` exists. */ public xadd( - key: string, - values: [string, string][], + key: GlideString, + values: [GlideString, GlideString][], options?: StreamAddOptions, ): T { return this.addAndReturn(createXAdd(key, values, options)); } + /** + * Removes the specified entries by id from a stream, and returns the number of entries deleted. + * + * @see {@link https://valkey.io/commands/xdel/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param ids - An array of entry ids. + * + * Command Response - The number of entries removed from the stream. This number may be less than the number of entries in + * `ids`, if the specified `ids` don't exist in the stream. + */ + public xdel(key: GlideString, ids: GlideString[]): T { + return this.addAndReturn(createXDel(key, ids)); + } + /** * Trims the stream stored at `key` by evicting older entries. - * See https://valkey.io/commands/xtrim/ for more details. + * @see {@link https://valkey.io/commands/xtrim/|valkey.io} for details. * * @param key - the key of the stream * @param options - options detailing how to trim the stream. - * @returns The number of entries deleted from the stream. If `key` doesn't exist, 0 is returned. + * + * Command Response - The number of entries deleted from the stream. If `key` doesn't exist, 0 is returned. */ public xtrim(key: string, options: StreamTrimOptions): T { return this.addAndReturn(createXTrim(key, options)); } - /** Returns the server time. - * See https://valkey.io/commands/time/ for details. - * - * @returns - The current server time as a two items `array`: - * A Unix timestamp and the amount of microseconds already elapsed in the current second. - * The returned `array` is in a [Unix timestamp, Microseconds already elapsed] format. - */ - public time(): T { - return this.addAndReturn(createTime()); - } - /** - * Reads entries from the given streams. - * See https://valkey.io/commands/xread/ for more details. + * Returns information about the stream stored at `key`. + * + * @param key - The key of the stream. + * @param fullOptions - If `true`, returns verbose information with a limit of the first 10 PEL entries. + * If `number` is specified, returns verbose information limiting the returned PEL entries. + * If `0` is specified, returns verbose information with no limit. * - * @param keys_and_ids - pairs of keys and entry ids to read from. A pair is composed of a stream's key and the id of the entry after which the stream will be read. - * @param options - options detailing how to read the stream. - * @returns A map between a stream key, and an array of entries in the matching key. The entries are in an [id, fields[]] format. + * Command Response - A {@link ReturnTypeXinfoStream} of detailed stream information for the given `key`. + * See example of {@link BaseClient.xinfoStream} for more details. */ - public xread( - keys_and_ids: Record, - options?: StreamReadOptions, - ): T { - return this.addAndReturn(createXRead(keys_and_ids, options)); + public xinfoStream(key: string, fullOptions?: boolean | number): T { + return this.addAndReturn(createXInfoStream(key, fullOptions ?? false)); } /** - * Returns the number of entries in the stream stored at `key`. + * Returns the list of all consumer groups and their attributes for the stream stored at `key`. * - * See https://valkey.io/commands/xlen/ for more details. + * @see {@link https://valkey.io/commands/xinfo-groups/|valkey.io} for details. * * @param key - The key of the stream. * - * Command Response - The number of entries in the stream. If `key` does not exist, returns `0`. + * Command Response - An `Array` of `Records`, where each mapping represents the + * attributes of a consumer group for the stream at `key`. */ - public xlen(key: string): T { - return this.addAndReturn(createXLen(key)); + public xinfoGroups(key: string): T { + return this.addAndReturn(createXInfoGroups(key)); } /** - * Renames `key` to `newkey`. - * If `newkey` already exists it is overwritten. - * In Cluster mode, both `key` and `newkey` must be in the same hash slot, - * meaning that in practice only keys that have the same hash tag can be reliably renamed in cluster. - * See https://valkey.io/commands/rename/ for more details. + * Returns the server time. + * + * @see {@link https://valkey.io/commands/time/|valkey.io} for details. + * + * Command Response - The current server time as an `array` with two items: + * - A Unix timestamp, + * - The amount of microseconds already elapsed in the current second. + */ + public time(): T { + return this.addAndReturn(createTime()); + } + + /** + * Returns stream entries matching a given range of entry IDs. + * + * @see {@link https://valkey.io/commands/xrange/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param start - The starting stream entry ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.NegativeInfinity` to start with the minimum available ID. + * @param end - The ending stream ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.PositiveInfinity` to end with the maximum available ID. + * @param count - An optional argument specifying the maximum count of stream entries to return. + * If `count` is not provided, all stream entries in the range will be returned. + * + * Command Response - A map of stream entry ids, to an array of entries, or `null` if `count` is non-positive. + */ + public xrange( + key: string, + start: Boundary, + end: Boundary, + count?: number, + ): T { + return this.addAndReturn(createXRange(key, start, end, count)); + } + + /** + * Returns stream entries matching a given range of entry IDs in reverse order. Equivalent to {@link xrange} but returns the + * entries in reverse order. + * + * @see {@link https://valkey.io/commands/xrevrange/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param end - The ending stream entry ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.PositiveInfinity` to end with the maximum available ID. + * @param start - The ending stream ID bound for the range. + * - Use `value` to specify a stream entry ID. + * - Use `isInclusive: false` to specify an exclusive bounded stream entry ID. This is only available starting with Valkey version 6.2.0. + * - Use `InfBoundary.NegativeInfinity` to start with the minimum available ID. + * @param count - An optional argument specifying the maximum count of stream entries to return. + * If `count` is not provided, all stream entries in the range will be returned. + * + * Command Response - A map of stream entry ids, to an array of entries, or `null` if `count` is non-positive. + */ + public xrevrange( + key: string, + end: Boundary, + start: Boundary, + count?: number, + ): T { + return this.addAndReturn(createXRevRange(key, end, start, count)); + } + + /** + * Reads entries from the given streams. + * + * @see {@link https://valkey.io/commands/xread/|valkey.io} for details. + * + * @param keys_and_ids - An object of stream keys and entry IDs to read from. + * @param options - (Optional) Parameters detailing how to read the stream - see {@link StreamReadOptions}. + * + * Command Response - A `Record` of stream keys, each key is mapped to a `Record` of stream ids, to an `Array` of entries. + */ + public xread( + keys_and_ids: Record, + options?: StreamReadOptions, + ): T { + return this.addAndReturn(createXRead(keys_and_ids, options)); + } + + /** + * Reads entries from the given streams owned by a consumer group. + * + * @see {@link https://valkey.io/commands/xreadgroup/|valkey.io} for details. + * + * @param group - The consumer group name. + * @param consumer - The group consumer. + * @param keys_and_ids - An object of stream keys and entry IDs to read from. + * Use the special ID of `">"` to receive only new messages. + * @param options - (Optional) Parameters detailing how to read the stream - see {@link StreamReadGroupOptions}. + * + * Command Response - A map of stream keys, each key is mapped to a map of stream ids, which is mapped to an array of entries. + * Returns `null` if there is no stream that can be served. + */ + public xreadgroup( + group: string, + consumer: string, + keys_and_ids: Record, + options?: StreamReadGroupOptions, + ): T { + return this.addAndReturn( + createXReadGroup(group, consumer, keys_and_ids, options), + ); + } + + /** + * Returns the number of entries in the stream stored at `key`. + * + * @see {@link https://valkey.io/commands/xlen/|valkey.io} for details. + * + * @param key - The key of the stream. + * + * Command Response - The number of entries in the stream. If `key` does not exist, returns `0`. + */ + public xlen(key: string): T { + return this.addAndReturn(createXLen(key)); + } + + /** + * Returns stream message summary information for pending messages matching a given range of IDs. + * + * @see {@link https://valkey.io/commands/xpending/|valkey.io} for details. + * Returns the list of all consumers and their attributes for the given consumer group of the + * stream stored at `key`. + * + * @see {@link https://valkey.io/commands/xinfo-consumers/|valkey.io} for details. + * + * @param key - The key of the stream. + * @param group - The consumer group name. + * + * Command Response - An `array` that includes the summary of the pending messages. + * See example of {@link BaseClient.xpending|xpending} for more details. + */ + public xpending(key: string, group: string): T { + return this.addAndReturn(createXPending(key, group)); + } + + /** + * Returns stream message summary information for pending messages matching a given range of IDs. + * + * @see {@link https://valkey.io/commands/xpending/|valkey.io} for details. + * + * @param key - The key of the stream. + * @param group - The consumer group name. + * @param options - Additional options to filter entries, see {@link StreamPendingOptions}. + * + * Command Response - A 2D-`array` of 4-tuples containing extended message information. + * See example of {@link BaseClient.xpendingWithOptions|xpendingWithOptions} for more details. + */ + public xpendingWithOptions( + key: string, + group: string, + options: StreamPendingOptions, + ): T { + return this.addAndReturn(createXPending(key, group, options)); + } + + /** + * Returns the list of all consumers and their attributes for the given consumer group of the + * stream stored at `key`. + * + * @see {@link https://valkey.io/commands/xinfo-consumers/|valkey.io} for details. + * + * Command Response - An `Array` of `Records`, where each mapping contains the attributes + * of a consumer for the given consumer group of the stream at `key`. + */ + public xinfoConsumers(key: string, group: string): T { + return this.addAndReturn(createXInfoConsumers(key, group)); + } + + /** + * Changes the ownership of a pending message. + * + * @see {@link https://valkey.io/commands/xclaim/|valkey.io} for details. + * + * @param key - The key of the stream. + * @param group - The consumer group name. + * @param consumer - The group consumer. + * @param minIdleTime - The minimum idle time for the message to be claimed. + * @param ids - An array of entry ids. + * @param options - (Optional) Stream claim options {@link StreamClaimOptions}. + * + * Command Response - A `Record` of message entries that are claimed by the consumer. + */ + public xclaim( + key: GlideString, + group: GlideString, + consumer: GlideString, + minIdleTime: number, + ids: GlideString[], + options?: StreamClaimOptions, + ): T { + return this.addAndReturn( + createXClaim(key, group, consumer, minIdleTime, ids, options), + ); + } + + /** + * Changes the ownership of a pending message. This function returns an `array` with + * only the message/entry IDs, and is equivalent to using `JUSTID` in the Valkey API. + * + * @see {@link https://valkey.io/commands/xclaim/|valkey.io} for details. + * + * @param key - The key of the stream. + * @param group - The consumer group name. + * @param consumer - The group consumer. + * @param minIdleTime - The minimum idle time for the message to be claimed. + * @param ids - An array of entry ids. + * @param options - (Optional) Stream claim options {@link StreamClaimOptions}. + * + * Command Response - An `array` of message ids claimed by the consumer. + */ + public xclaimJustId( + key: string, + group: string, + consumer: string, + minIdleTime: number, + ids: string[], + options?: StreamClaimOptions, + ): T { + return this.addAndReturn( + createXClaim(key, group, consumer, minIdleTime, ids, options, true), + ); + } + + /** + * Transfers ownership of pending stream entries that match the specified criteria. + * + * @see {@link https://valkey.io/commands/xautoclaim/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the stream. + * @param group - The consumer group name. + * @param consumer - The group consumer. + * @param minIdleTime - The minimum idle time for the message to be claimed. + * @param start - Filters the claimed entries to those that have an ID equal or greater than the + * specified value. + * @param count - (Optional) Limits the number of claimed entries to the specified value. + * + * Command Response - An `array` containing the following elements: + * - A stream ID to be used as the start argument for the next call to `XAUTOCLAIM`. This ID is + * equivalent to the next ID in the stream after the entries that were scanned, or "0-0" if + * the entire stream was scanned. + * - A mapping of the claimed entries. + * - If you are using Valkey 7.0.0 or above, the response list will also include a list containing + * the message IDs that were in the Pending Entries List but no longer exist in the stream. + * These IDs are deleted from the Pending Entries List. + */ + public xautoclaim( + key: GlideString, + group: GlideString, + consumer: GlideString, + minIdleTime: number, + start: GlideString, + count?: number, + ): T { + return this.addAndReturn( + createXAutoClaim(key, group, consumer, minIdleTime, start, count), + ); + } + + /** + * Transfers ownership of pending stream entries that match the specified criteria. + * + * @see {@link https://valkey.io/commands/xautoclaim/|valkey.io} for more details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the stream. + * @param group - The consumer group name. + * @param consumer - The group consumer. + * @param minIdleTime - The minimum idle time for the message to be claimed. + * @param start - Filters the claimed entries to those that have an ID equal or greater than the + * specified value. + * @param count - (Optional) Limits the number of claimed entries to the specified value. + * + * Command Response - An `array` containing the following elements: + * - A stream ID to be used as the start argument for the next call to `XAUTOCLAIM`. This ID is + * equivalent to the next ID in the stream after the entries that were scanned, or "0-0" if + * the entire stream was scanned. + * - A list of the IDs for the claimed entries. + * - If you are using Valkey 7.0.0 or above, the response list will also include a list containing + * the message IDs that were in the Pending Entries List but no longer exist in the stream. + * These IDs are deleted from the Pending Entries List. + */ + public xautoclaimJustId( + key: string, + group: string, + consumer: string, + minIdleTime: number, + start: string, + count?: number, + ): T { + return this.addAndReturn( + createXAutoClaim( + key, + group, + consumer, + minIdleTime, + start, + count, + true, + ), + ); + } + + /** + * Creates a new consumer group uniquely identified by `groupname` for the stream + * stored at `key`. + * + * @see {@link https://valkey.io/commands/xgroup-create/|valkey.io} for details. + * + * @param key - The key of the stream. + * @param groupName - The newly created consumer group name. + * @param id - Stream entry ID that specifies the last delivered entry in the stream from the new + * group’s perspective. The special ID `"$"` can be used to specify the last entry in the stream. + * + * Command Response - `"OK"`. + */ + public xgroupCreate( + key: GlideString, + groupName: GlideString, + id: GlideString, + options?: StreamGroupOptions, + ): T { + return this.addAndReturn( + createXGroupCreate(key, groupName, id, options), + ); + } + + /** + * Destroys the consumer group `groupname` for the stream stored at `key`. + * + * @see {@link https://valkey.io/commands/xgroup-destroy/|valkey.io} for details. + * + * @param key - The key of the stream. + * @param groupname - The newly created consumer group name. + * + * Command Response - `true` if the consumer group is destroyed. Otherwise, `false`. + */ + public xgroupDestroy(key: GlideString, groupName: GlideString): T { + return this.addAndReturn(createXGroupDestroy(key, groupName)); + } + + /** + * Creates a consumer named `consumerName` in the consumer group `groupName` for the stream stored at `key`. + * + * @see {@link https://valkey.io/commands/xgroup-createconsumer/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param groupName - The consumer group name. + * @param consumerName - The newly created consumer. + * + * Command Response - `true` if the consumer is created. Otherwise, returns `false`. + */ + public xgroupCreateConsumer( + key: GlideString, + groupName: GlideString, + consumerName: GlideString, + ): T { + return this.addAndReturn( + createXGroupCreateConsumer(key, groupName, consumerName), + ); + } + + /** + * Deletes a consumer named `consumerName` in the consumer group `groupName` for the stream stored at `key`. + * + * @see {@link https://valkey.io/commands/xgroup-delconsumer/|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param groupName - The consumer group name. + * @param consumerName - The consumer to delete. + * + * Command Response - The number of pending messages the `consumer` had before it was deleted. + */ + public xgroupDelConsumer( + key: GlideString, + groupName: GlideString, + consumerName: GlideString, + ): T { + return this.addAndReturn( + createXGroupDelConsumer(key, groupName, consumerName), + ); + } + + /** + * Returns the number of messages that were successfully acknowledged by the consumer group member of a stream. + * This command should be called on a pending message so that such message does not get processed again. + * + * @see {@link https://valkey.io/commands/xack/|valkey.io} for details. + * + * @param key - The key of the stream. + * @param group - The consumer group name. + * @param ids - An array of entry ids. + * + * Command Response - The number of messages that were successfully acknowledged. + */ + public xack(key: GlideString, group: GlideString, ids: GlideString[]): T { + return this.addAndReturn(createXAck(key, group, ids)); + } + + /** + * Sets the last delivered ID for a consumer group. + * + * @see {@link https://valkey.io/commands/xgroup-setid|valkey.io} for more details. + * + * @param key - The key of the stream. + * @param groupName - The consumer group name. + * @param id - The stream entry ID that should be set as the last delivered ID for the consumer group. + * @param entriesRead - (Optional) A value representing the number of stream entries already read by the group. + * This option can only be specified if you are using Valkey version 7.0.0 or above. + * + * Command Response - `"OK"`. + */ + public xgroupSetId( + key: GlideString, + groupName: GlideString, + id: GlideString, + entriesRead?: number, + ): T { + return this.addAndReturn( + createXGroupSetid(key, groupName, id, entriesRead), + ); + } + + /** + * Renames `key` to `newkey`. + * If `newkey` already exists it is overwritten. + * + * @see {@link https://valkey.io/commands/rename/|valkey.io} for details. * * @param key - The key to rename. * @param newKey - The new name of the key. + * * Command Response - If the `key` was successfully renamed, return "OK". If `key` does not exist, an error is thrown. */ - public rename(key: string, newKey: string): T { + public rename(key: GlideString, newKey: GlideString): T { return this.addAndReturn(createRename(key, newKey)); } /** * Renames `key` to `newkey` if `newkey` does not yet exist. - * In Cluster mode, both `key` and `newkey` must be in the same hash slot, - * meaning that in practice only keys that have the same hash tag can be reliably renamed in cluster. - * See https://valkey.io/commands/renamenx/ for more details. + * + * @see {@link https://valkey.io/commands/renamenx/|valkey.io} for details. * * @param key - The key to rename. * @param newKey - The new name of the key. + * * Command Response - If the `key` was successfully renamed, returns `true`. Otherwise, returns `false`. - * If `key` does not exist, an error is thrown. + * If `key` does not exist, an error is thrown. */ - public renamenx(key: string, newKey: string): T { + public renamenx(key: GlideString, newKey: GlideString): T { return this.addAndReturn(createRenameNX(key, newKey)); } @@ -1493,16 +3040,17 @@ export class BaseTransaction> { * Pop an element from the tail of the first list that is non-empty, * with the given `keys` being checked in the order that they are given. * Blocks the connection when there are no elements to pop from any of the given lists. - * See https://valkey.io/commands/brpop/ for more details. - * Note: `BRPOP` is a blocking command, - * see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices. + * + * @see {@link https://valkey.io/commands/brpop/|valkey.io} for details. + * @remarks `BRPOP` is a blocking command, see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices. * * @param keys - The `keys` of the lists to pop from. * @param timeout - The `timeout` in seconds. + * * Command Response - An `array` containing the `key` from which the element was popped and the value of the popped element, * formatted as [key, value]. If no element could be popped and the timeout expired, returns `null`. */ - public brpop(keys: string[], timeout: number): T { + public brpop(keys: GlideString[], timeout: number): T { return this.addAndReturn(createBRPop(keys, timeout)); } @@ -1510,16 +3058,17 @@ export class BaseTransaction> { * Pop an element from the head of the first list that is non-empty, * with the given `keys` being checked in the order that they are given. * Blocks the connection when there are no elements to pop from any of the given lists. - * See https://valkey.io/commands/blpop/ for more details. - * Note: `BLPOP` is a blocking command, - * see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices. + * + * @see {@link https://valkey.io/commands/blpop/|valkey.io} for details. + * @remarks `BLPOP` is a blocking command, see [Blocking Commands](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands) for more details and best practices. * * @param keys - The `keys` of the lists to pop from. * @param timeout - The `timeout` in seconds. + * * Command Response - An `array` containing the `key` from which the element was popped and the value of the popped element, * formatted as [key, value]. If no element could be popped and the timeout expired, returns `null`. */ - public blpop(keys: string[], timeout: number): T { + public blpop(keys: GlideString[], timeout: number): T { return this.addAndReturn(createBLPop(keys, timeout)); } @@ -1527,128 +3076,1135 @@ export class BaseTransaction> { * Creates a new structure if the `key` does not exist. * When no elements are provided, and `key` exists and is a HyperLogLog, then no operation is performed. * - * See https://valkey.io/commands/pfadd/ for more details. + * @see {@link https://valkey.io/commands/pfadd/|valkey.io} for details. * * @param key - The key of the HyperLogLog data structure to add elements into. * @param elements - An array of members to add to the HyperLogLog stored at `key`. * Command Response - If the HyperLogLog is newly created, or if the HyperLogLog approximated cardinality is * altered, then returns `1`. Otherwise, returns `0`. */ - public pfadd(key: string, elements: string[]): T { + public pfadd(key: GlideString, elements: GlideString[]): T { return this.addAndReturn(createPfAdd(key, elements)); } /** Estimates the cardinality of the data stored in a HyperLogLog structure for a single key or * calculates the combined cardinality of multiple keys by merging their HyperLogLogs temporarily. * - * See https://valkey.io/commands/pfcount/ for more details. + * @see {@link https://valkey.io/commands/pfcount/|valkey.io} for details. * * @param keys - The keys of the HyperLogLog data structures to be analyzed. * Command Response - The approximated cardinality of given HyperLogLog data structures. * The cardinality of a key that does not exist is `0`. */ - public pfcount(keys: string[]): T { + public pfcount(keys: GlideString[]): T { return this.addAndReturn(createPfCount(keys)); } - /** Returns the internal encoding for the Redis object stored at `key`. + /** + * Merges multiple HyperLogLog values into a unique value. If the destination variable exists, it is + * treated as one of the source HyperLogLog data sets, otherwise a new HyperLogLog is created. + * + * @see {@link https://valkey.io/commands/pfmerge/|valkey.io} for details. + * + * @param destination - The key of the destination HyperLogLog where the merged data sets will be stored. + * @param sourceKeys - The keys of the HyperLogLog structures to be merged. + * Command Response - A simple "OK" response. + */ + public pfmerge(destination: GlideString, sourceKeys: GlideString[]): T { + return this.addAndReturn(createPfMerge(destination, sourceKeys)); + } + + /** + * Returns the internal encoding for the Redis object stored at `key`. * - * See https://valkey.io/commands/object-encoding for more details. + * @see {@link https://valkey.io/commands/object-encoding/|valkey.io} for more details. * * @param key - The `key` of the object to get the internal encoding of. + * * Command Response - If `key` exists, returns the internal encoding of the object stored at `key` as a string. * Otherwise, returns None. */ - public objectEncoding(key: string): T { + public objectEncoding(key: GlideString): T { return this.addAndReturn(createObjectEncoding(key)); } - /** Returns the logarithmic access frequency counter of a Redis object stored at `key`. + /** + * Returns the logarithmic access frequency counter of a Redis object stored at `key`. * - * See https://valkey.io/commands/object-freq for more details. + * @see {@link https://valkey.io/commands/object-freq/|valkey.io} for more details. * * @param key - The `key` of the object to get the logarithmic access frequency counter of. + * * Command Response - If `key` exists, returns the logarithmic access frequency counter of * the object stored at `key` as a `number`. Otherwise, returns `null`. */ - public objectFreq(key: string): T { + public objectFreq(key: GlideString): T { return this.addAndReturn(createObjectFreq(key)); } /** * Returns the time in seconds since the last access to the value stored at `key`. * - * See https://valkey.io/commands/object-idletime/ for more details. + * @see {@link https://valkey.io/commands/object-idletime/|valkey.io} for details. * * @param key - The key of the object to get the idle time of. * * Command Response - If `key` exists, returns the idle time in seconds. Otherwise, returns `null`. */ - public objectIdletime(key: string): T { + public objectIdletime(key: GlideString): T { return this.addAndReturn(createObjectIdletime(key)); } /** * Returns the reference count of the object stored at `key`. * - * See https://valkey.io/commands/object-refcount/ for more details. + * @see {@link https://valkey.io/commands/object-refcount/|valkey.io} for details. * * @param key - The `key` of the object to get the reference count of. * * Command Response - If `key` exists, returns the reference count of the object stored at `key` as a `number`. - * Otherwise, returns `null`. + * Otherwise, returns `null`. */ - public objectRefcount(key: string): T { + public objectRefcount(key: GlideString): T { return this.addAndReturn(createObjectRefcount(key)); } -} -/** - * Extends BaseTransaction class for Redis standalone commands. - * Transactions allow the execution of a group of commands in a single step. - * - * Command Response: - * An array of command responses is returned by the GlideClient.exec command, in the order they were given. - * Each element in the array represents a command given to the transaction. - * The response for each command depends on the executed Redis command. - * Specific response types are documented alongside each method. - * - * @example - * ```typescript - * const transaction = new Transaction() - * .set("key", "value") - * .select(1) /// Standalone command - * .get("key"); - * const result = await GlideClient.exec(transaction); - * console.log(result); // Output: ['OK', 'OK', null] - * ``` - */ -export class Transaction extends BaseTransaction { - /// TODO: add MOVE, SLAVEOF and all SENTINEL commands + /** + * Displays a piece of generative computer art and the server version. + * + * @see {@link https://valkey.io/commands/lolwut/|valkey.io} for details. + * + * @param options - (Optional) The LOLWUT options - see {@link LolwutOptions}. + * + * Command Response - A piece of generative computer art along with the current server version. + */ + public lolwut(options?: LolwutOptions): T { + return this.addAndReturn(createLolwut(options)); + } - /** Change the currently selected Redis database. - * See https://valkey.io/commands/select/ for details. + /** + * Blocks the current client until all the previous write commands are successfully transferred and + * acknowledged by at least `numreplicas` of replicas. If `timeout` is reached, the command returns + * the number of replicas that were not yet reached. * - * @param index - The index of the database to select. + * @see {@link https://valkey.io/commands/wait/|valkey.io} for more details. * - * Command Response - A simple OK response. + * @param numreplicas - The number of replicas to reach. + * @param timeout - The timeout value specified in milliseconds. A value of 0 will block indefinitely. + * + * Command Response - The number of replicas reached by all the writes performed in the context of the + * current connection. */ - public select(index: number): Transaction { - return this.addAndReturn(createSelect(index)); + public wait(numreplicas: number, timeout: number): T { + return this.addAndReturn(createWait(numreplicas, timeout)); } -} -/** - * Extends BaseTransaction class for cluster mode commands. - * Transactions allow the execution of a group of commands in a single step. - * - * Command Response: - * An array of command responses is returned by the GlideClusterClient.exec command, in the order they were given. - * Each element in the array represents a command given to the transaction. - * The response for each command depends on the executed Redis command. + /** + * Invokes a previously loaded function. + * + * @see {@link https://valkey.io/commands/fcall/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param func - The function name. + * @param keys - A list of `keys` accessed by the function. To ensure the correct execution of functions, + * all names of keys that a function accesses must be explicitly provided as `keys`. + * @param args - A list of `function` arguments and it should not represent names of keys. + * + * Command Response - The invoked function's return value. + */ + public fcall( + func: GlideString, + keys: GlideString[], + args: GlideString[], + ): T { + return this.addAndReturn(createFCall(func, keys, args)); + } + + /** + * Invokes a previously loaded read-only function. + * + * @see {@link https://valkey.io/commands/fcall/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param func - The function name. + * @param keys - A list of `keys` accessed by the function. To ensure the correct execution of functions, + * all names of keys that a function accesses must be explicitly provided as `keys`. + * @param args - A list of `function` arguments and it should not represent names of keys. + * + * Command Response - The invoked function's return value. + */ + public fcallReadonly( + func: GlideString, + keys: GlideString[], + args: GlideString[], + ): T { + return this.addAndReturn(createFCallReadOnly(func, keys, args)); + } + + /** + * Deletes a library and all its functions. + * + * @see {@link https://valkey.io/commands/function-delete/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param libraryCode - The library name to delete. + * + * Command Response - `"OK"`. + */ + public functionDelete(libraryCode: GlideString): T { + return this.addAndReturn(createFunctionDelete(libraryCode)); + } + + /** + * Loads a library to Valkey. + * + * @see {@link https://valkey.io/commands/function-load/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param libraryCode - The source code that implements the library. + * @param replace - (Optional) Whether the given library should overwrite a library with the same name if it + * already exists. + * + * Command Response - The library name that was loaded. + */ + public functionLoad(libraryCode: GlideString, replace?: boolean): T { + return this.addAndReturn(createFunctionLoad(libraryCode, replace)); + } + + /** + * Deletes all function libraries. + * + * @see {@link https://valkey.io/commands/function-flush/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param mode - (Optional) The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}. + * Command Response - `"OK"`. + */ + public functionFlush(mode?: FlushMode): T { + return this.addAndReturn(createFunctionFlush(mode)); + } + + /** + * Returns information about the functions and libraries. + * + * @see {@link https://valkey.io/commands/function-list/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param options - (Optional) Parameters to filter and request additional info. + * + * Command Response - Info about all or selected libraries and their functions in {@link FunctionListResponse} format. + */ + public functionList(options?: FunctionListOptions): T { + return this.addAndReturn(createFunctionList(options)); + } + + /** + * Returns information about the function that's currently running and information about the + * available execution engines. + * + * @see {@link https://valkey.io/commands/function-stats/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * Command Response - A `Record` of type {@link FunctionStatsSingleResponse} with two keys: + * + * - `"running_script"` with information about the running script. + * - `"engines"` with information about available engines and their stats. + */ + public functionStats(): T { + return this.addAndReturn(createFunctionStats()); + } + + /** + * Returns the serialized payload of all loaded libraries. + * + * @see {@link https://valkey.io/commands/function-dump/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * @remarks To execute a transaction with a `functionDump` command, the `exec` command requires `Decoder.Bytes` to handle the response. + * + * Command Response - The serialized payload of all loaded libraries. + */ + public functionDump(): T { + return this.addAndReturn(createFunctionDump()); + } + + /** + * Restores libraries from the serialized payload returned by {@link functionDump}. + * + * @see {@link https://valkey.io/commands/function-restore/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param payload - The serialized data from {@link functionDump}. + * @param policy - (Optional) A policy for handling existing libraries. + * + * Command Response - `"OK"`. + */ + public functionRestore(payload: Buffer, policy?: FunctionRestorePolicy): T { + return this.addAndReturn(createFunctionRestore(payload, policy)); + } + + /** + * Deletes all the keys of all the existing databases. This command never fails. + * + * @see {@link https://valkey.io/commands/flushall/|valkey.io} for details. + * + * @param mode - (Optional) The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}. + * + * Command Response - `"OK"`. + */ + public flushall(mode?: FlushMode): T { + return this.addAndReturn(createFlushAll(mode)); + } + + /** + * Deletes all the keys of the currently selected database. This command never fails. + * + * @see {@link https://valkey.io/commands/flushdb/|valkey.io} for details. + * + * @param mode - (Optional) The flushing mode, could be either {@link FlushMode.SYNC} or {@link FlushMode.ASYNC}. + * + * Command Response - `"OK"`. + */ + public flushdb(mode?: FlushMode): T { + return this.addAndReturn(createFlushDB(mode)); + } + + /** + * Returns the index of the first occurrence of `element` inside the list specified by `key`. If no + * match is found, `null` is returned. If the `count` option is specified, then the function returns + * an `array` of indices of matching elements within the list. + * + * @see {@link https://valkey.io/commands/lpos/|valkey.io} for details. + * @remarks Since Valkey version 6.0.6. + * + * @param key - The name of the list. + * @param element - The value to search for within the list. + * @param options - (Optional) The LPOS options - see {@link LPosOptions}. + * + * Command Response - The index of `element`, or `null` if `element` is not in the list. If the `count` + * option is specified, then the function returns an `array` of indices of matching elements within the list. + */ + public lpos( + key: GlideString, + element: GlideString, + options?: LPosOptions, + ): T { + return this.addAndReturn(createLPos(key, element, options)); + } + + /** + * Returns the number of keys in the currently selected database. + * + * @see {@link https://valkey.io/commands/dbsize/|valkey.io} for details. + * + * Command Response - The number of keys in the currently selected database. + */ + public dbsize(): T { + return this.addAndReturn(createDBSize()); + } + + /** + * Counts the number of set bits (population counting) in the string stored at `key`. The `options` argument can + * optionally be provided to count the number of bits in a specific string interval. + * + * @see {@link https://valkey.io/commands/bitcount/|valkey.io} for more details. + * + * @param key - The key for the string to count the set bits of. + * @param options - The offset options. + * + * Command Response - If `options` is provided, returns the number of set bits in the string interval specified by `options`. + * If `options` is not provided, returns the number of set bits in the string stored at `key`. + * Otherwise, if `key` is missing, returns `0` as it is treated as an empty string. + */ + public bitcount(key: GlideString, options?: BitOffsetOptions): T { + return this.addAndReturn(createBitCount(key, options)); + } + + /** + * Adds geospatial members with their positions to the specified sorted set stored at `key`. + * If a member is already a part of the sorted set, its position is updated. + * + * @see {@link https://valkey.io/commands/geoadd/|valkey.io} for details. + * + * @param key - The key of the sorted set. + * @param membersToGeospatialData - A mapping of member names to their corresponding positions - see + * {@link GeospatialData}. The command will report an error when the user attempts to index + * coordinates outside the specified ranges. + * @param options - The GeoAdd options - see {@link GeoAddOptions}. + * + * Command Response - The number of elements added to the sorted set. If `changed` is set to + * `true` in the options, returns the number of elements updated in the sorted set. + */ + public geoadd( + key: GlideString, + membersToGeospatialData: Map, + options?: GeoAddOptions, + ): T { + return this.addAndReturn( + createGeoAdd(key, membersToGeospatialData, options), + ); + } + + /** + * Returns the members of a sorted set populated with geospatial information using {@link geoadd}, + * which are within the borders of the area specified by a given shape. + * + * @see {@link https://valkey.io/commands/geosearch/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param key - The key of the sorted set. + * @param searchFrom - The query's center point options, could be one of: + * + * - {@link MemberOrigin} to use the position of the given existing member in the sorted set. + * + * - {@link CoordOrigin} to use the given longitude and latitude coordinates. + * + * @param searchBy - The query's shape options, could be one of: + * + * - {@link GeoCircleShape} to search inside circular area according to given radius. + * + * - {@link GeoBoxShape} to search inside an axis-aligned rectangle, determined by height and width. + * + * @param resultOptions - The optional inputs to request additional information and configure sorting/limiting the results, see {@link GeoSearchResultOptions}. + * + * Command Response - By default, returns an `Array` of members (locations) names. + * If any of `withCoord`, `withDist` or `withHash` are set to `true` in {@link GeoSearchResultOptions}, a 2D `Array` returned, + * where each sub-array represents a single item in the following order: + * + * - The member (location) name. + * - The distance from the center as a floating point `number`, in the same unit specified for `searchBy`. + * - The geohash of the location as a integer `number`. + * - The coordinates as a two item `array` of floating point `number`s. + */ + public geosearch( + key: GlideString, + searchFrom: SearchOrigin, + searchBy: GeoSearchShape, + resultOptions?: GeoSearchResultOptions, + ): T { + return this.addAndReturn( + createGeoSearch(key, searchFrom, searchBy, resultOptions), + ); + } + + /** + * Searches for members in a sorted set stored at `source` representing geospatial data + * within a circular or rectangular area and stores the result in `destination`. + * + * If `destination` already exists, it is overwritten. Otherwise, a new sorted set will be created. + * + * To get the result directly, see {@link geosearch}. + * + * @see {@link https://valkey.io/commands/geosearchstore/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param destination - The key of the destination sorted set. + * @param source - The key of the sorted set. + * @param searchFrom - The query's center point options, could be one of: + * - {@link MemberOrigin} to use the position of the given existing member in the sorted set. + * - {@link CoordOrigin} to use the given longitude and latitude coordinates. + * @param searchBy - The query's shape options, could be one of: + * - {@link GeoCircleShape} to search inside circular area according to given radius. + * - {@link GeoBoxShape} to search inside an axis-aligned rectangle, determined by height and width. + * @param resultOptions - (Optional) Parameters to request additional information and configure sorting/limiting the results, see {@link GeoSearchStoreResultOptions}. + * + * Command Response - The number of elements in the resulting sorted set stored at `destination`. + */ + public geosearchstore( + destination: GlideString, + source: GlideString, + searchFrom: SearchOrigin, + searchBy: GeoSearchShape, + resultOptions?: GeoSearchStoreResultOptions, + ): T { + return this.addAndReturn( + createGeoSearchStore( + destination, + source, + searchFrom, + searchBy, + resultOptions, + ), + ); + } + + /** + * Returns the positions (longitude, latitude) of all the specified `members` of the + * geospatial index represented by the sorted set at `key`. + * + * @see {@link https://valkey.io/commands/geopos/|valkey.io} for more details. + * + * @param key - The key of the sorted set. + * @param members - The members for which to get the positions. + * + * Command Response - A 2D `Array` which represents positions (longitude and latitude) corresponding to the + * given members. The order of the returned positions matches the order of the input members. + * If a member does not exist, its position will be `null`. + */ + public geopos(key: GlideString, members: GlideString[]): T { + return this.addAndReturn(createGeoPos(key, members)); + } + + /** + * Pops a member-score pair from the first non-empty sorted set, with the given `keys` + * being checked in the order they are provided. + * + * @see {@link https://valkey.io/commands/zmpop/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param keys - The keys of the sorted sets. + * @param modifier - The element pop criteria - either {@link ScoreFilter.MIN} or + * {@link ScoreFilter.MAX} to pop the member with the lowest/highest score accordingly. + * @param count - (Optional) The number of elements to pop. If not supplied, only one element will be popped. + * + * Command Response - A two-element `array` containing the key name of the set from which the + * element was popped, and a member-score `Record` of the popped element. + * If no member could be popped, returns `null`. + */ + public zmpop(keys: string[], modifier: ScoreFilter, count?: number): T { + return this.addAndReturn(createZMPop(keys, modifier, count)); + } + + /** + * Pops a member-score pair from the first non-empty sorted set, with the given `keys` being + * checked in the order they are provided. Blocks the connection when there are no members + * to pop from any of the given sorted sets. `BZMPOP` is the blocking variant of {@link zmpop}. + * + * @see {@link https://valkey.io/commands/bzmpop/|valkey.io} for details. + * @remarks `BZMPOP` is a client blocking command, see {@link https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands | Valkey Glide Wiki} for more details and best practices. + * @remarks Since Valkey version 7.0.0. + * + * @param keys - The keys of the sorted sets. + * @param modifier - The element pop criteria - either {@link ScoreFilter.MIN} or + * {@link ScoreFilter.MAX} to pop the member with the lowest/highest score accordingly. + * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of `0` will block indefinitely. + * @param count - (Optional) The number of elements to pop. If not supplied, only one element will be popped. + * + * Command Response - A two-element `array` containing the key name of the set from which the element + * was popped, and a member-score `Record` of the popped element. + * If no member could be popped, returns `null`. + */ + public bzmpop( + keys: GlideString[], + modifier: ScoreFilter, + timeout: number, + count?: number, + ): T { + return this.addAndReturn(createBZMPop(keys, modifier, timeout, count)); + } + + /** + * Increments the score of `member` in the sorted set stored at `key` by `increment`. + * If `member` does not exist in the sorted set, it is added with `increment` as its score. + * If `key` does not exist, a new sorted set is created with the specified member as its sole member. + * + * @see {@link https://valkey.io/commands/zincrby/|valkey.io} for details. + * + * @param key - The key of the sorted set. + * @param increment - The score increment. + * @param member - A member of the sorted set. + * + * Command Response - The new score of `member`. + */ + public zincrby( + key: GlideString, + increment: number, + member: GlideString, + ): T { + return this.addAndReturn(createZIncrBy(key, increment, member)); + } + + /** + * Iterates incrementally over a sorted set. + * + * @see {@link https://valkey.io/commands/zscan/|valkey.io} for more details. + * + * @param key - The key of the sorted set. + * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of + * the search. + * @param options - (Optional) The zscan options. + * + * Command Response - An `Array` of the `cursor` and the subset of the sorted set held by `key`. + * The first element is always the `cursor` for the next iteration of results. `0` will be the `cursor` + * returned on the last iteration of the sorted set. The second element is always an `Array` of the subset + * of the sorted set held in `key`. The `Array` in the second element is always a flattened series of + * `String` pairs, where the value is at even indices and the score is at odd indices. + */ + public zscan(key: string, cursor: string, options?: BaseScanOptions): T { + return this.addAndReturn(createZScan(key, cursor, options)); + } + + /** + * Returns the distance between `member1` and `member2` saved in the geospatial index stored at `key`. + * + * @see {@link https://valkey.io/commands/geodist/|valkey.io} for details. + * + * @param key - The key of the sorted set. + * @param member1 - The name of the first member. + * @param member2 - The name of the second member. + * @param geoUnit - The unit of distance measurement - see {@link GeoUnit}. If not specified, the default unit is {@link GeoUnit.METERS}. + * + * Command Response - The distance between `member1` and `member2`. Returns `null`, if one or both members do not exist, + * or if the key does not exist. + */ + public geodist( + key: GlideString, + member1: GlideString, + member2: GlideString, + geoUnit?: GeoUnit, + ): T { + return this.addAndReturn(createGeoDist(key, member1, member2, geoUnit)); + } + + /** + * Returns the `GeoHash` strings representing the positions of all the specified `members` in the sorted set stored at `key`. + * + * @see {@link https://valkey.io/commands/geohash/|valkey.io} for details. + * + * @param key - The key of the sorted set. + * @param members - The array of members whose `GeoHash` strings are to be retrieved. + * + * Command Response - An array of `GeoHash` strings representing the positions of the specified members stored at `key`. + * If a member does not exist in the sorted set, a `null` value is returned for that member. + */ + public geohash(key: GlideString, members: GlideString[]): T { + return this.addAndReturn(createGeoHash(key, members)); + } + + /** + * Returns `UNIX TIME` of the last DB save timestamp or startup timestamp if no save + * was made since then. + * + * @see {@link https://valkey.io/commands/lastsave/|valkey.io} for details. + * + * Command Response - `UNIX TIME` of the last DB save executed with success. + */ + public lastsave(): T { + return this.addAndReturn(createLastSave()); + } + + /** + * Returns all the longest common subsequences combined between strings stored at `key1` and `key2`. + * + * @see {@link https://valkey.io/commands/lcs/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param key1 - The key that stores the first string. + * @param key2 - The key that stores the second string. + * + * Command Response - A `String` containing all the longest common subsequence combined between the 2 strings. + * An empty `String` is returned if the keys do not exist or have no common subsequences. + */ + public lcs(key1: GlideString, key2: GlideString): T { + return this.addAndReturn(createLCS(key1, key2)); + } + + /** + * Returns the total length of all the longest common subsequences between strings stored at `key1` and `key2`. + * + * @see {@link https://valkey.io/commands/lcs/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param key1 - The key that stores the first string. + * @param key2 - The key that stores the second string. + * + * Command Response - The total length of all the longest common subsequences between the 2 strings. + */ + public lcsLen(key1: GlideString, key2: GlideString): T { + return this.addAndReturn(createLCS(key1, key2, { len: true })); + } + + /** + * Returns the indices and lengths of the longest common subsequences between strings stored at + * `key1` and `key2`. + * + * @see {@link https://valkey.io/commands/lcs/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param key1 - The key that stores the first string. + * @param key2 - The key that stores the second string. + * @param options - (Optional) Additional parameters: + * - (Optional) `withMatchLen`: if `true`, include the length of the substring matched for the each match. + * - (Optional) `minMatchLen`: the minimum length of matches to include in the result. + * + * Command Response - A `Record` containing the indices of the longest common subsequences between the + * 2 strings and the lengths of the longest common subsequences. The resulting map contains two + * keys, "matches" and "len": + * - `"len"` is mapped to the total length of the all longest common subsequences between the 2 strings + * stored as an integer. This value doesn't count towards the `minMatchLen` filter. + * - `"matches"` is mapped to a three dimensional array of integers that stores pairs + * of indices that represent the location of the common subsequences in the strings held + * by `key1` and `key2`. + * + * See example of {@link BaseClient.lcsIdx|lcsIdx} for more details. + */ + public lcsIdx( + key1: GlideString, + key2: GlideString, + options?: { withMatchLen?: boolean; minMatchLen?: number }, + ): T { + return this.addAndReturn(createLCS(key1, key2, { idx: options ?? {} })); + } + + /** + * Updates the last access time of the specified keys. + * + * @see {@link https://valkey.io/commands/touch/|valkey.io} for details. + * + * @param keys - The keys to update the last access time of. + * + * Command Response - The number of keys that were updated. A key is ignored if it doesn't exist. + */ + public touch(keys: GlideString[]): T { + return this.addAndReturn(createTouch(keys)); + } + + /** + * Returns a random existing key name from the currently selected database. + * + * @see {@link https://valkey.io/commands/randomkey/|valkey.io} for details. + * + * Command Response - A random existing key name from the currently selected database. + */ + public randomKey(): T { + return this.addAndReturn(createRandomKey()); + } + + /** + * Overwrites part of the string stored at `key`, starting at the specified `offset`, + * for the entire length of `value`. If the `offset` is larger than the current length of the string at `key`, + * the string is padded with zero bytes to make `offset` fit. Creates the `key` if it doesn't exist. + * + * @see {@link https://valkey.io/commands/setrange/|valkey.io} for details. + * + * @param key - The key of the string to update. + * @param offset - The position in the string where `value` should be written. + * @param value - The string written with `offset`. + * + * Command Response - The length of the string stored at `key` after it was modified. + */ + public setrange(key: GlideString, offset: number, value: GlideString): T { + return this.addAndReturn(createSetRange(key, offset, value)); + } + + /** + * Appends a `value` to a `key`. If `key` does not exist it is created and set as an empty string, + * so `APPEND` will be similar to {@link set} in this special case. + * + * @see {@link https://valkey.io/commands/append/|valkey.io} for details. + * + * @param key - The key of the string. + * @param value - The key of the string. + * + * Command Response - The length of the string after appending the value. + */ + public append(key: GlideString, value: GlideString): T { + return this.addAndReturn(createAppend(key, value)); + } + + /** + * Pops one or more elements from the first non-empty list from the provided `keys`. + * + * @see {@link https://valkey.io/commands/lmpop/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param keys - An array of keys to lists. + * @param direction - The direction based on which elements are popped from - see {@link ListDirection}. + * @param count - (Optional) The maximum number of popped elements. + * + * Command Response - A `Record` of `key` name mapped array of popped elements. + */ + public lmpop( + keys: GlideString[], + direction: ListDirection, + count?: number, + ): T { + return this.addAndReturn(createLMPop(keys, direction, count)); + } + + /** + * Blocks the connection until it pops one or more elements from the first non-empty list from the + * provided `key`. `BLMPOP` is the blocking variant of {@link lmpop}. + * + * @see {@link https://valkey.io/commands/blmpop/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param keys - An array of keys to lists. + * @param direction - The direction based on which elements are popped from - see {@link ListDirection}. + * @param timeout - The number of seconds to wait for a blocking operation to complete. A value of + * `0` will block indefinitely. + * @param count - (Optional) The maximum number of popped elements. + * + * Command Response - A `Record` of `key` name mapped array of popped elements. + * If no member could be popped and the timeout expired, returns `null`. + */ + public blmpop( + keys: GlideString[], + direction: ListDirection, + timeout: number, + count?: number, + ): T { + return this.addAndReturn(createBLMPop(keys, direction, timeout, count)); + } + + /** + * Lists the currently active channels. + * The command is routed to all nodes, and aggregates the response to a single array. + * + * @see {@link https://valkey.io/commands/pubsub-channels/|valkey.io} for more details. + * + * @param pattern - A glob-style pattern to match active channels. + * If not provided, all active channels are returned. + * Command Response - A list of currently active channels matching the given pattern. + * If no pattern is specified, all active channels are returned. + */ + public pubsubChannels(pattern?: GlideString): T { + return this.addAndReturn(createPubSubChannels(pattern)); + } + + /** + * Returns the number of unique patterns that are subscribed to by clients. + * + * Note: This is the total number of unique patterns all the clients are subscribed to, + * not the count of clients subscribed to patterns. + * The command is routed to all nodes, and aggregates the response to the sum of all pattern subscriptions. + * + * @see {@link https://valkey.io/commands/pubsub-numpat/|valkey.io} for more details. + * + * Command Response - The number of unique patterns. + */ + public pubsubNumPat(): T { + return this.addAndReturn(createPubSubNumPat()); + } + + /** + * Returns the number of subscribers (exclusive of clients subscribed to patterns) for the specified channels. + * + * Note that it is valid to call this command without channels. In this case, it will just return an empty map. + * The command is routed to all nodes, and aggregates the response to a single map of the channels and their number of subscriptions. + * + * @see {@link https://valkey.io/commands/pubsub-numsub/|valkey.io} for more details. + * + * @param channels - The list of channels to query for the number of subscribers. + * If not provided, returns an empty map. + * Command Response - A map where keys are the channel names and values are the number of subscribers. + */ + public pubsubNumSub(channels?: string[]): T { + return this.addAndReturn(createPubSubNumSub(channels)); + } +} + +/** + * Extends BaseTransaction class for Redis standalone commands. + * Transactions allow the execution of a group of commands in a single step. + * + * Command Response: + * An array of command responses is returned by the GlideClient.exec command, in the order they were given. + * Each element in the array represents a command given to the transaction. + * The response for each command depends on the executed Redis command. + * Specific response types are documented alongside each method. + * + * @example + * ```typescript + * const transaction = new Transaction() + * .set("key", "value") + * .select(1) /// Standalone command + * .get("key"); + * const result = await GlideClient.exec(transaction); + * console.log(result); // Output: ['OK', 'OK', null] + * ``` + */ +export class Transaction extends BaseTransaction { + /// TODO: add MOVE, SLAVEOF and all SENTINEL commands + + /** + * Change the currently selected database. + * + * @see {@link https://valkey.io/commands/select/|valkey.io} for details. + * + * @param index - The index of the database to select. + * + * Command Response - A simple `"OK"` response. + */ + public select(index: number): Transaction { + return this.addAndReturn(createSelect(index)); + } + + /** + * Sorts the elements in the list, set, or sorted set at `key` and returns the result. + * + * The `sort` command can be used to sort elements based on different criteria and + * apply transformations on sorted elements. + * + * To store the result into a new key, see {@link sortStore}. + * + * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details. + * + * @param key - The key of the list, set, or sorted set to be sorted. + * @param options - (Optional) {@link SortOptions}. + * + * Command Response - An `Array` of sorted elements. + */ + public sort(key: GlideString, options?: SortOptions): Transaction { + return this.addAndReturn(createSort(key, options)); + } + + /** + * Sorts the elements in the list, set, or sorted set at `key` and returns the result. + * + * The `sortReadOnly` command can be used to sort elements based on different criteria and + * apply transformations on sorted elements. + * + * This command is routed depending on the client's {@link ReadFrom} strategy. + * + * @remarks Since Valkey version 7.0.0. + * + * @param key - The key of the list, set, or sorted set to be sorted. + * @param options - (Optional) {@link SortOptions}. + * + * Command Response - An `Array` of sorted elements + */ + public sortReadOnly(key: GlideString, options?: SortOptions): Transaction { + return this.addAndReturn(createSortReadOnly(key, options)); + } + + /** + * Sorts the elements in the list, set, or sorted set at `key` and stores the result in + * `destination`. + * + * The `sort` command can be used to sort elements based on different criteria and + * apply transformations on sorted elements, and store the result in a new key. + * + * To get the sort result without storing it into a key, see {@link sort} or {@link sortReadOnly}. + * + * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details. + * + * @param key - The key of the list, set, or sorted set to be sorted. + * @param destination - The key where the sorted result will be stored. + * @param options - (Optional) {@link SortOptions}. + * + * Command Response - The number of elements in the sorted key stored at `destination`. + */ + public sortStore( + key: GlideString, + destination: GlideString, + options?: SortOptions, + ): Transaction { + return this.addAndReturn(createSort(key, options, destination)); + } + + /** + * Copies the value stored at the `source` to the `destination` key. If `destinationDB` is specified, + * the value will be copied to the database specified, otherwise the current database will be used. + * When `replace` is true, removes the `destination` key first if it already exists, otherwise performs + * no action. + * + * @see {@link https://valkey.io/commands/copy/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param source - The key to the source value. + * @param destination - The key where the value should be copied to. + * @param destinationDB - (Optional) The alternative logical database index for the destination key. + * If not provided, the current database will be used. + * @param replace - (Optional) If `true`, the `destination` key should be removed before copying the + * value to it. If not provided, no action will be performed if the key already exists. + * + * Command Response - `true` if `source` was copied, `false` if the `source` was not copied. + */ + public copy( + source: GlideString, + destination: GlideString, + options?: { destinationDB?: number; replace?: boolean }, + ): Transaction { + return this.addAndReturn(createCopy(source, destination, options)); + } + + /** + * Move `key` from the currently selected database to the database specified by `dbIndex`. + * + * @see {@link https://valkey.io/commands/move/|valkey.io} for details. + * + * @param key - The key to move. + * @param dbIndex - The index of the database to move `key` to. + * + * Command Response - `true` if `key` was moved, or `false` if the `key` already exists in the destination + * database or does not exist in the source database. + */ + public move(key: GlideString, dbIndex: number): Transaction { + return this.addAndReturn(createMove(key, dbIndex)); + } + + /** Publish a message on pubsub channel. + * + * @see {@link https://valkey.io/commands/publish/|valkey.io} for more details. + * + * @param message - Message to publish. + * @param channel - Channel to publish the message on. + * + * Command Response - Number of subscriptions in primary node that received the message. + * Note that this value does not include subscriptions that configured on replicas. + */ + public publish(message: GlideString, channel: GlideString): Transaction { + return this.addAndReturn(createPublish(message, channel)); + } +} + +/** + * Extends BaseTransaction class for cluster mode commands. + * Transactions allow the execution of a group of commands in a single step. + * + * Command Response: + * An array of command responses is returned by the GlideClusterClient.exec command, in the order they were given. + * Each element in the array represents a command given to the transaction. + * The response for each command depends on the executed Redis command. * Specific response types are documented alongside each method. * */ export class ClusterTransaction extends BaseTransaction { /// TODO: add all CLUSTER commands + + /** + * Sorts the elements in the list, set, or sorted set at `key` and returns the result. + * + * The `sort` command can be used to sort elements based on different criteria and + * apply transformations on sorted elements. + * + * To store the result into a new key, see {@link sortStore}. + * + * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details. + * + * @param key - The key of the list, set, or sorted set to be sorted. + * @param options - (Optional) {@link SortClusterOptions}. + * + * Command Response - An `Array` of sorted elements. + */ + public sort( + key: GlideString, + options?: SortClusterOptions, + ): ClusterTransaction { + return this.addAndReturn(createSort(key, options)); + } + + /** + * Sorts the elements in the list, set, or sorted set at `key` and returns the result. + * + * The `sortReadOnly` command can be used to sort elements based on different criteria and + * apply transformations on sorted elements. + * + * This command is routed depending on the client's {@link ReadFrom} strategy. + * + * @see {@link https://valkey.io/commands/sort/|valkey.io} for more details. + * @remarks Since Valkey version 7.0.0. + * + * @param key - The key of the list, set, or sorted set to be sorted. + * @param options - (Optional) {@link SortClusterOptions}. + * + * Command Response - An `Array` of sorted elements + */ + public sortReadOnly( + key: GlideString, + options?: SortClusterOptions, + ): ClusterTransaction { + return this.addAndReturn(createSortReadOnly(key, options)); + } + + /** + * Sorts the elements in the list, set, or sorted set at `key` and stores the result in + * `destination`. + * + * The `sort` command can be used to sort elements based on different criteria and + * apply transformations on sorted elements, and store the result in a new key. + * + * To get the sort result without storing it into a key, see {@link sort} or {@link sortReadOnly}. + * + * @see {@link https://valkey.io/commands/sort|valkey.io} for more details. + * + * @param key - The key of the list, set, or sorted set to be sorted. + * @param destination - The key where the sorted result will be stored. + * @param options - (Optional) {@link SortClusterOptions}. + * + * Command Response - The number of elements in the sorted key stored at `destination`. + */ + public sortStore( + key: GlideString, + destination: GlideString, + options?: SortClusterOptions, + ): ClusterTransaction { + return this.addAndReturn(createSort(key, options, destination)); + } + + /** + * Copies the value stored at the `source` to the `destination` key. When `replace` is true, + * removes the `destination` key first if it already exists, otherwise performs no action. + * + * @see {@link https://valkey.io/commands/copy/|valkey.io} for details. + * @remarks Since Valkey version 6.2.0. + * + * @param source - The key to the source value. + * @param destination - The key where the value should be copied to. + * @param replace - (Optional) If `true`, the `destination` key should be removed before copying the + * value to it. If not provided, no action will be performed if the key already exists. + * + * Command Response - `true` if `source` was copied, `false` if the `source` was not copied. + */ + public copy( + source: GlideString, + destination: GlideString, + replace?: boolean, + ): ClusterTransaction { + return this.addAndReturn( + createCopy(source, destination, { replace: replace }), + ); + } + + /** Publish a message on pubsub channel. + * This command aggregates PUBLISH and SPUBLISH commands functionalities. + * The mode is selected using the 'sharded' parameter. + * For both sharded and non-sharded mode, request is routed using hashed channel as key. + * + * @see {@link https://valkey.io/commands/publish} and {@link https://valkey.io/commands/spublish} for more details. + * + * @param message - Message to publish. + * @param channel - Channel to publish the message on. + * @param sharded - Use sharded pubsub mode. Available since Valkey version 7.0. + * + * Command Response - Number of subscriptions in primary node that received the message. + */ + public publish( + message: GlideString, + channel: GlideString, + sharded: boolean = false, + ): ClusterTransaction { + return this.addAndReturn(createPublish(message, channel, sharded)); + } + + /** + * Lists the currently active shard channels. + * The command is routed to all nodes, and aggregates the response to a single array. + * + * @see {@link https://valkey.io/commands/pubsub-shardchannels|valkey.io} for more details. + * + * @param pattern - A glob-style pattern to match active shard channels. + * If not provided, all active shard channels are returned. + * Command Response - A list of currently active shard channels matching the given pattern. + * If no pattern is specified, all active shard channels are returned. + */ + public pubsubShardChannels(pattern?: GlideString): ClusterTransaction { + return this.addAndReturn(createPubsubShardChannels(pattern)); + } + + /** + * Returns the number of subscribers (exclusive of clients subscribed to patterns) for the specified shard channels. + * + * Note that it is valid to call this command without channels. In this case, it will just return an empty map. + * The command is routed to all nodes, and aggregates the response to a single map of the channels and their number of subscriptions. + * + * @see {@link https://valkey.io/commands/pubsub-shardnumsub|valkey.io} for more details. + * + * @param channels - The list of shard channels to query for the number of subscribers. + * If not provided, returns an empty map. + * @returns A map where keys are the shard channel names and values are the number of subscribers. + */ + public pubsubShardNumSub(channels?: string[]): ClusterTransaction { + return this.addAndReturn(createPubSubShardNumSub(channels)); + } } diff --git a/node/tests/AsyncClient.test.ts b/node/tests/AsyncClient.test.ts index ec75809878..6d0d3b4246 100644 --- a/node/tests/AsyncClient.test.ts +++ b/node/tests/AsyncClient.test.ts @@ -7,13 +7,11 @@ import { AsyncClient } from "glide-rs"; import RedisServer from "redis-server"; import { runCommonTests } from "./SharedTests"; import { flushallOnPort } from "./TestUtilities"; -/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ const FreePort = require("find-free-port"); const PORT_NUMBER = 4000; -type EmptyObject = Record; - describe("AsyncClient", () => { let server: RedisServer; let port: number; @@ -41,7 +39,7 @@ describe("AsyncClient", () => { server.close(); }); - runCommonTests({ + runCommonTests({ init: async () => { const client = await AsyncClient.CreateConnection( "redis://localhost:" + port, diff --git a/node/tests/GlideClient.test.ts b/node/tests/GlideClient.test.ts new file mode 100644 index 0000000000..8efc797900 --- /dev/null +++ b/node/tests/GlideClient.test.ts @@ -0,0 +1,1619 @@ +/** + * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + */ + +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + it, +} from "@jest/globals"; +import { BufferReader, BufferWriter } from "protobufjs"; +import { v4 as uuidv4 } from "uuid"; +import { + Decoder, + GlideClient, + HashDataType, + ProtocolVersion, + RequestError, + Transaction, +} from ".."; +import { RedisCluster } from "../../utils/TestUtils.js"; +import { + FlushMode, + FunctionRestorePolicy, + SortOrder, +} from "../build-ts/src/Commands"; +import { command_request } from "../src/ProtobufMessage"; +import { runBaseTests } from "./SharedTests"; +import { + checkFunctionListResponse, + checkFunctionStatsResponse, + convertStringArrayToBuffer, + createLuaLibWithLongRunningFunction, + DumpAndRestureTest, + encodableTransactionTest, + encodedTransactionTest, + flushAndCloseClient, + generateLuaLibCode, + getClientConfigurationOption, + parseCommandLineArgs, + parseEndpoints, + transactionTest, + validateTransactionResponse, + waitForNotBusy, +} from "./TestUtilities"; + +const TIMEOUT = 50000; + +describe("GlideClient", () => { + let testsFailed = 0; + let cluster: RedisCluster; + let client: GlideClient; + beforeAll(async () => { + const standaloneAddresses = + parseCommandLineArgs()["standalone-endpoints"]; + // Connect to cluster or create a new one based on the parsed addresses + cluster = standaloneAddresses + ? await RedisCluster.initFromExistingCluster( + parseEndpoints(standaloneAddresses), + ) + : await RedisCluster.createCluster(false, 1, 1); + }, 20000); + + afterEach(async () => { + await flushAndCloseClient(false, cluster.getAddresses(), client); + }); + + afterAll(async () => { + if (testsFailed === 0) { + await cluster.close(); + } + }, TIMEOUT); + + it("test protobuf encode/decode delimited", () => { + // This test is required in order to verify that the autogenerated protobuf + // files has been corrected and the encoding/decoding works as expected. + // See "Manually compile protobuf files" in node/README.md to get more info about the fix. + const writer = new BufferWriter(); + const request = { + callbackIdx: 1, + singleCommand: { + requestType: 2, + argsArray: command_request.Command.ArgsArray.create({ + args: convertStringArrayToBuffer(["bar1", "bar2"]), + }), + }, + }; + const request2 = { + callbackIdx: 3, + singleCommand: { + requestType: 4, + argsArray: command_request.Command.ArgsArray.create({ + args: convertStringArrayToBuffer(["bar3", "bar4"]), + }), + }, + }; + command_request.CommandRequest.encodeDelimited(request, writer); + command_request.CommandRequest.encodeDelimited(request2, writer); + const buffer = writer.finish(); + const reader = new BufferReader(buffer); + + const dec_msg1 = command_request.CommandRequest.decodeDelimited(reader); + expect(dec_msg1.callbackIdx).toEqual(1); + expect(dec_msg1.singleCommand?.requestType).toEqual(2); + expect(dec_msg1.singleCommand?.argsArray?.args).toEqual( + convertStringArrayToBuffer(["bar1", "bar2"]), + ); + + const dec_msg2 = command_request.CommandRequest.decodeDelimited(reader); + expect(dec_msg2.callbackIdx).toEqual(3); + expect(dec_msg2.singleCommand?.requestType).toEqual(4); + expect(dec_msg2.singleCommand?.argsArray?.args).toEqual( + convertStringArrayToBuffer(["bar3", "bar4"]), + ); + }); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "info without parameters", + async (protocol) => { + client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const result = await client.info(); + expect(result).toEqual(expect.stringContaining("# Server")); + expect(result).toEqual(expect.stringContaining("# Replication")); + expect(result).toEqual( + expect.not.stringContaining("# Latencystats"), + ); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "select dbsize flushdb test %p", + async (protocol) => { + client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + expect(await client.select(0)).toEqual("OK"); + + const key = uuidv4(); + const value = uuidv4(); + const result = await client.set(key, value); + expect(result).toEqual("OK"); + + expect(await client.select(1)).toEqual("OK"); + expect(await client.get(key)).toEqual(null); + expect(await client.flushdb()).toEqual("OK"); + expect(await client.dbsize()).toEqual(0); + + expect(await client.select(0)).toEqual("OK"); + expect(await client.get(key)).toEqual(value); + + expect(await client.dbsize()).toBeGreaterThan(0); + expect(await client.flushdb(FlushMode.SYNC)).toEqual("OK"); + expect(await client.dbsize()).toEqual(0); + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "bytes decoder client test %p", + async (protocol) => { + const clientConfig = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + ); + clientConfig.defaultDecoder = Decoder.Bytes; + client = await GlideClient.createClient(clientConfig); + expect(await client.select(0)).toEqual("OK"); + + const key = uuidv4(); + const value = uuidv4(); + const valueEncoded = Buffer.from(value); + const result = await client.set(key, value); + expect(result).toEqual("OK"); + + expect(await client.get(key)).toEqual(valueEncoded); + expect(await client.get(key, Decoder.String)).toEqual(value); + expect(await client.get(key, Decoder.Bytes)).toEqual(valueEncoded); + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "string decoder client test %p", + async (protocol) => { + const clientConfig = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + ); + clientConfig.defaultDecoder = Decoder.String; + client = await GlideClient.createClient(clientConfig); + expect(await client.select(0)).toEqual("OK"); + + const key = uuidv4(); + const value = uuidv4(); + const valueEncoded = Buffer.from(value); + const result = await client.set(key, value); + expect(result).toEqual("OK"); + + expect(await client.get(key)).toEqual(value); + expect(await client.get(key, Decoder.String)).toEqual(value); + expect(await client.get(key, Decoder.Bytes)).toEqual(valueEncoded); + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `can send transactions_%p`, + async (protocol) => { + client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const transaction = new Transaction(); + const expectedRes = await transactionTest( + transaction, + cluster.getVersion(), + ); + transaction.select(0); + const result = await client.exec(transaction); + expectedRes.push(["select(0)", "OK"]); + + validateTransactionResponse(result, expectedRes); + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `can get Bytes decoded transactions_%p`, + async (protocol) => { + client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const transaction = new Transaction(); + const expectedRes = await encodedTransactionTest(transaction); + transaction.select(0); + const result = await client.exec(transaction, Decoder.Bytes); + expectedRes.push(["select(0)", "OK"]); + + validateTransactionResponse(result, expectedRes); + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `dump and restore transactions_%p`, + async (protocol) => { + client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const bytesTransaction = new Transaction(); + const expectedBytesRes = await DumpAndRestureTest( + bytesTransaction, + Buffer.from("value"), + ); + bytesTransaction.select(0); + const result = await client.exec(bytesTransaction, Decoder.Bytes); + expectedBytesRes.push(["select(0)", "OK"]); + + validateTransactionResponse(result, expectedBytesRes); + + const stringTransaction = new Transaction(); + await DumpAndRestureTest(stringTransaction, "value"); + stringTransaction.select(0); + + // Since DUMP gets binary results, we cannot use the string decoder here, so we expected to get an error. + await expect( + client.exec(stringTransaction, Decoder.String), + ).rejects.toThrowError( + "invalid utf-8 sequence of 1 bytes from index 9", + ); + + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `can send transaction with default string decoder_%p`, + async (protocol) => { + const clientConfig = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + ); + clientConfig.defaultDecoder = Decoder.String; + client = await GlideClient.createClient(clientConfig); + expect(await client.select(0)).toEqual("OK"); + const transaction = new Transaction(); + const expectedRes = await encodableTransactionTest( + transaction, + "value", + ); + transaction.select(0); + const result = await client.exec(transaction); + expectedRes.push(["select(0)", "OK"]); + + validateTransactionResponse(result, expectedRes); + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `can send transaction with default bytes decoder_%p`, + async (protocol) => { + const clientConfig = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + ); + clientConfig.defaultDecoder = Decoder.Bytes; + client = await GlideClient.createClient(clientConfig); + expect(await client.select(0)).toEqual("OK"); + const transaction = new Transaction(); + const valueEncoded = Buffer.from("value"); + const expectedRes = await encodableTransactionTest( + transaction, + valueEncoded, + ); + transaction.select(0); + const result = await client.exec(transaction); + expectedRes.push(["select(0)", "OK"]); + + validateTransactionResponse(result, expectedRes); + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "can return null on WATCH transaction failures", + async (protocol) => { + const client1 = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const client2 = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const transaction = new Transaction(); + transaction.get("key"); + const result1 = await client1.watch(["key"]); + expect(result1).toEqual("OK"); + + const result2 = await client2.set("key", "foo"); + expect(result2).toEqual("OK"); + + const result3 = await client1.exec(transaction); + expect(result3).toBeNull(); + + client1.close(); + client2.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "object freq transaction test_%p", + async (protocol) => { + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key = uuidv4(); + const maxmemoryPolicyKey = "maxmemory-policy"; + const config = await client.configGet([maxmemoryPolicyKey]); + const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); + + try { + const transaction = new Transaction(); + transaction.configSet({ + [maxmemoryPolicyKey]: "allkeys-lfu", + }); + transaction.set(key, "foo"); + transaction.objectFreq(key); + + const response = await client.exec(transaction); + expect(response).not.toBeNull(); + + if (response != null) { + expect(response.length).toEqual(3); + expect(response[0]).toEqual("OK"); + expect(response[1]).toEqual("OK"); + expect(response[2]).toBeGreaterThanOrEqual(0); + } + } finally { + expect( + await client.configSet({ + [maxmemoryPolicyKey]: maxmemoryPolicy, + }), + ).toEqual("OK"); + } + + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "object idletime transaction test_%p", + async (protocol) => { + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key = uuidv4(); + const maxmemoryPolicyKey = "maxmemory-policy"; + const config = await client.configGet([maxmemoryPolicyKey]); + const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); + + try { + const transaction = new Transaction(); + transaction.configSet({ + // OBJECT IDLETIME requires a non-LFU maxmemory-policy + [maxmemoryPolicyKey]: "allkeys-random", + }); + transaction.set(key, "foo"); + transaction.objectIdletime(key); + + const response = await client.exec(transaction); + expect(response).not.toBeNull(); + + if (response != null) { + expect(response.length).toEqual(3); + // transaction.configSet({[maxmemoryPolicyKey]: "allkeys-random"}); + expect(response[0]).toEqual("OK"); + // transaction.set(key, "foo"); + expect(response[1]).toEqual("OK"); + // transaction.objectIdletime(key); + expect(response[2]).toBeGreaterThanOrEqual(0); + } + } finally { + expect( + await client.configSet({ + [maxmemoryPolicyKey]: maxmemoryPolicy, + }), + ).toEqual("OK"); + } + + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "object refcount transaction test_%p", + async (protocol) => { + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key = uuidv4(); + const transaction = new Transaction(); + transaction.set(key, "foo"); + transaction.objectRefcount(key); + + const response = await client.exec(transaction); + expect(response).not.toBeNull(); + + if (response != null) { + expect(response.length).toEqual(2); + expect(response[0]).toEqual("OK"); // transaction.set(key, "foo"); + expect(response[1]).toBeGreaterThanOrEqual(1); // transaction.objectRefcount(key); + } + + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "lolwut test_%p", + async (protocol) => { + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const result = await client.lolwut(); + expect(result).toEqual(expect.stringContaining("Redis ver. ")); + + const result2 = await client.lolwut({ parameters: [] }); + expect(result2).toEqual(expect.stringContaining("Redis ver. ")); + + const result3 = await client.lolwut({ parameters: [50, 20] }); + expect(result3).toEqual(expect.stringContaining("Redis ver. ")); + + const result4 = await client.lolwut({ version: 6 }); + expect(result4).toEqual(expect.stringContaining("Redis ver. ")); + + const result5 = await client.lolwut({ + version: 5, + parameters: [30, 4, 4], + }); + expect(result5).toEqual(expect.stringContaining("Redis ver. ")); + + // transaction tests + const transaction = new Transaction(); + transaction.lolwut(); + transaction.lolwut({ version: 5 }); + transaction.lolwut({ parameters: [1, 2] }); + transaction.lolwut({ version: 6, parameters: [42] }); + const results = await client.exec(transaction); + + if (results) { + for (const element of results) { + expect(element).toEqual( + expect.stringContaining("Redis ver. "), + ); + } + } else { + throw new Error("Invalid LOLWUT transaction test results."); + } + + client.close(); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "copy with DB test_%p", + async (protocol) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const source = `{key}-${uuidv4()}`; + const destination = `{key}-${uuidv4()}`; + const value1 = uuidv4(); + const value2 = uuidv4(); + const index0 = 0; + const index1 = 1; + const index2 = 2; + + // neither key exists + expect( + await client.copy(source, destination, { + destinationDB: index1, + replace: false, + }), + ).toEqual(false); + + // source exists, destination does not + expect(await client.set(source, value1)).toEqual("OK"); + expect( + await client.copy(source, destination, { + destinationDB: index1, + replace: false, + }), + ).toEqual(true); + expect(await client.select(index1)).toEqual("OK"); + expect(await client.get(destination)).toEqual(value1); + + // new value for source key + expect(await client.select(index0)).toEqual("OK"); + expect(await client.set(source, value2)).toEqual("OK"); + + // no REPLACE, copying to existing key on DB 1, non-existing key on DB 2 + expect( + await client.copy(Buffer.from(source), destination, { + destinationDB: index1, + replace: false, + }), + ).toEqual(false); + expect( + await client.copy(source, Buffer.from(destination), { + destinationDB: index2, + replace: false, + }), + ).toEqual(true); + + // new value only gets copied to DB 2 + expect(await client.select(index1)).toEqual("OK"); + expect(await client.get(destination)).toEqual(value1); + expect(await client.select(index2)).toEqual("OK"); + expect(await client.get(destination)).toEqual(value2); + + // both exists, with REPLACE, when value isn't the same, source always get copied to + // destination + expect(await client.select(index0)).toEqual("OK"); + expect( + await client.copy( + Buffer.from(source), + Buffer.from(destination), + { + destinationDB: index1, + replace: true, + }, + ), + ).toEqual(true); + expect(await client.select(index1)).toEqual("OK"); + expect(await client.get(destination)).toEqual(value2); + + //transaction tests + const transaction = new Transaction(); + transaction.select(index1); + transaction.set(source, value1); + transaction.copy(source, destination, { + destinationDB: index1, + replace: true, + }); + transaction.get(destination); + const results = await client.exec(transaction); + + expect(results).toEqual(["OK", "OK", true, value1]); + + client.close(); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "move test_%p", + async (protocol) => { + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const value = uuidv4(); + + expect(await client.select(0)).toEqual("OK"); + expect(await client.move(key1, 1)).toEqual(false); + + expect(await client.set(key1, value)).toEqual("OK"); + expect(await client.get(key1)).toEqual(value); + expect(await client.move(Buffer.from(key1), 1)).toEqual(true); + expect(await client.get(key1)).toEqual(null); + expect(await client.select(1)).toEqual("OK"); + expect(await client.get(key1)).toEqual(value); + + await expect(client.move(key1, -1)).rejects.toThrow(RequestError); + + //transaction tests + const transaction = new Transaction(); + transaction.select(1); + transaction.move(key2, 0); + transaction.set(key2, value); + transaction.move(key2, 0); + transaction.select(0); + transaction.get(key2); + const results = await client.exec(transaction); + + expect(results).toEqual(["OK", false, "OK", true, "OK", value]); + + client.close(); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "function load function list function stats test_%p", + async (protocol) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + try { + const libName = "mylib1C" + uuidv4().replaceAll("-", ""); + const funcName = "myfunc1c" + uuidv4().replaceAll("-", ""); + const code = generateLuaLibCode( + libName, + new Map([[funcName, "return args[1]"]]), + true, + ); + expect(await client.functionList()).toEqual([]); + + expect(await client.functionLoad(Buffer.from(code))).toEqual( + libName, + ); + + expect( + await client.fcall( + Buffer.from(funcName), + [], + [Buffer.from("one"), "two"], + Decoder.Bytes, + ), + ).toEqual(Buffer.from("one")); + expect( + await client.fcallReadonly( + Buffer.from(funcName), + [], + ["one", Buffer.from("two")], + Decoder.Bytes, + ), + ).toEqual(Buffer.from("one")); + + let functionStats = await client.functionStats(); + + for (const response of Object.values(functionStats)) { + checkFunctionStatsResponse(response, [], 1, 1); + } + + let functionList = await client.functionList({ + libNamePattern: Buffer.from(libName), + }); + let expectedDescription = new Map([ + [funcName, null], + ]); + let expectedFlags = new Map([ + [funcName, ["no-writes"]], + ]); + + checkFunctionListResponse( + functionList, + libName, + expectedDescription, + expectedFlags, + ); + + // re-load library without replace + await expect(client.functionLoad(code)).rejects.toThrow( + `Library '${libName}' already exists`, + ); + + // re-load library with replace + expect( + await client.functionLoad(code, { + replace: true, + decoder: Decoder.Bytes, + }), + ).toEqual(Buffer.from(libName)); + + // overwrite lib with new code + const func2Name = "myfunc2c" + uuidv4().replaceAll("-", ""); + const newCode = generateLuaLibCode( + libName, + new Map([ + [funcName, "return args[1]"], + [func2Name, "return #args"], + ]), + true, + ); + expect( + await client.functionLoad(newCode, { replace: true }), + ).toEqual(libName); + + functionList = await client.functionList({ withCode: true }); + expectedDescription = new Map([ + [funcName, null], + [func2Name, null], + ]); + expectedFlags = new Map([ + [funcName, ["no-writes"]], + [func2Name, ["no-writes"]], + ]); + + checkFunctionListResponse( + functionList, + libName, + expectedDescription, + expectedFlags, + newCode, + ); + + functionStats = await client.functionStats(); + + for (const response of Object.values(functionStats)) { + checkFunctionStatsResponse(response, [], 1, 2); + } + + expect( + await client.fcall(func2Name, [], ["one", "two"]), + ).toEqual(2); + expect( + await client.fcallReadonly(func2Name, [], ["one", "two"]), + ).toEqual(2); + } finally { + expect(await client.functionFlush()).toEqual("OK"); + const functionStats = await client.functionStats(); + + for (const response of Object.values(functionStats)) { + checkFunctionStatsResponse(response, [], 0, 0); + } + + client.close(); + } + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "function flush test_%p", + async (protocol) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + try { + const libName = "mylib1C" + uuidv4().replaceAll("-", ""); + const funcName = "myfunc1c" + uuidv4().replaceAll("-", ""); + const code = generateLuaLibCode( + libName, + new Map([[funcName, "return args[1]"]]), + true, + ); + + // verify function does not yet exist + expect(await client.functionList()).toEqual([]); + + expect(await client.functionLoad(code)).toEqual(libName); + + // Flush functions + expect(await client.functionFlush(FlushMode.SYNC)).toEqual( + "OK", + ); + expect(await client.functionFlush(FlushMode.ASYNC)).toEqual( + "OK", + ); + + // verify function does not yet exist + expect(await client.functionList()).toEqual([]); + + // Attempt to re-load library without overwriting to ensure FLUSH was effective + expect(await client.functionLoad(code)).toEqual(libName); + } finally { + expect(await client.functionFlush()).toEqual("OK"); + client.close(); + } + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "function delete test_%p", + async (protocol) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + try { + const libName = "mylib1C" + uuidv4().replaceAll("-", ""); + const funcName = "myfunc1c" + uuidv4().replaceAll("-", ""); + const code = generateLuaLibCode( + libName, + new Map([[funcName, "return args[1]"]]), + true, + ); + // verify function does not yet exist + expect(await client.functionList()).toEqual([]); + + expect(await client.functionLoad(code)).toEqual(libName); + + // Delete the function + expect(await client.functionDelete(libName)).toEqual("OK"); + + // verify function does not yet exist + expect(await client.functionList()).toEqual([]); + + // deleting a non-existing library + await expect(client.functionDelete(libName)).rejects.toThrow( + `Library not found`, + ); + } finally { + expect(await client.functionFlush()).toEqual("OK"); + client.close(); + } + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "function kill RO func %p", + async (protocol) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + + const config = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + { requestTimeout: 10000 }, + ); + const client = await GlideClient.createClient(config); + const testClient = await GlideClient.createClient(config); + + try { + const libName = "function_kill_no_write"; + const funcName = "deadlock_no_write"; + const code = createLuaLibWithLongRunningFunction( + libName, + funcName, + 6, + true, + ); + expect(await client.functionFlush()).toEqual("OK"); + // nothing to kill + await expect(client.functionKill()).rejects.toThrow(/notbusy/i); + + // load the lib + expect( + await client.functionLoad(code, { replace: true }), + ).toEqual(libName); + + try { + // call the function without await + const promise = testClient + .fcall(funcName, [], []) + .catch((e) => + expect((e as Error).message).toContain( + "Script killed", + ), + ); + + let killed = false; + let timeout = 4000; + await new Promise((resolve) => setTimeout(resolve, 1000)); + + while (timeout >= 0) { + try { + expect(await client.functionKill()).toEqual("OK"); + killed = true; + break; + } catch { + // do nothing + } + + await new Promise((resolve) => + setTimeout(resolve, 500), + ); + timeout -= 500; + } + + expect(killed).toBeTruthy(); + await promise; + } finally { + await waitForNotBusy(client); + } + } finally { + expect(await client.functionFlush()).toEqual("OK"); + testClient.close(); + client.close(); + } + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "function kill RW func %p", + async (protocol) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + + const config = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + { requestTimeout: 10000 }, + ); + const client = await GlideClient.createClient(config); + const testClient = await GlideClient.createClient(config); + + try { + const libName = "function_kill_write"; + const key = libName; + const funcName = "deadlock_write"; + const code = createLuaLibWithLongRunningFunction( + libName, + funcName, + 6, + false, + ); + expect(await client.functionFlush()).toEqual("OK"); + // nothing to kill + await expect(client.functionKill()).rejects.toThrow(/notbusy/i); + + // load the lib + expect( + await client.functionLoad(code, { replace: true }), + ).toEqual(libName); + + let promise = null; + + try { + // call the function without await + promise = testClient.fcall(funcName, [key], []); + + let foundUnkillable = false; + let timeout = 4000; + await new Promise((resolve) => setTimeout(resolve, 1000)); + + while (timeout >= 0) { + try { + // valkey kills a function with 5 sec delay + // but this will always throw an error in the test + await client.functionKill(); + } catch (err) { + // looking for an error with "unkillable" in the message + // at that point we can break the loop + if ( + (err as Error).message + .toLowerCase() + .includes("unkillable") + ) { + foundUnkillable = true; + break; + } + } + + await new Promise((resolve) => + setTimeout(resolve, 500), + ); + timeout -= 500; + } + + expect(foundUnkillable).toBeTruthy(); + } finally { + // If function wasn't killed, and it didn't time out - it blocks the server and cause rest + // test to fail. Wait for the function to complete (we cannot kill it) + expect(await promise).toContain("Timed out"); + } + } finally { + expect(await client.functionFlush()).toEqual("OK"); + testClient.close(); + client.close(); + } + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "function dump function restore %p", + async (protocol) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + + const config = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + ); + const client = await GlideClient.createClient(config); + expect(await client.functionFlush()).toEqual("OK"); + + try { + // dumping an empty lib + expect( + (await client.functionDump()).byteLength, + ).toBeGreaterThan(0); + + const name1 = "Foster"; + const name2 = "Dogster"; + // function $name1 returns first argument + // function $name2 returns argument array len + let code = generateLuaLibCode( + name1, + new Map([ + [name1, "return args[1]"], + [name2, "return #args"], + ]), + false, + ); + expect(await client.functionLoad(code)).toEqual(name1); + + const flist = await client.functionList({ withCode: true }); + const dump = await client.functionDump(); + + // restore without cleaning the lib and/or overwrite option causes an error + await expect(client.functionRestore(dump)).rejects.toThrow( + `Library ${name1} already exists`, + ); + + // APPEND policy also fails for the same reason (name collision) + await expect( + client.functionRestore(dump, FunctionRestorePolicy.APPEND), + ).rejects.toThrow(`Library ${name1} already exists`); + + // REPLACE policy succeeds + expect( + await client.functionRestore( + dump, + FunctionRestorePolicy.REPLACE, + ), + ).toEqual("OK"); + // but nothing changed - all code overwritten + expect(await client.functionList({ withCode: true })).toEqual( + flist, + ); + + // create lib with another name, but with the same function names + expect(await client.functionFlush(FlushMode.SYNC)).toEqual( + "OK", + ); + code = generateLuaLibCode( + name2, + new Map([ + [name1, "return args[1]"], + [name2, "return #args"], + ]), + false, + ); + expect(await client.functionLoad(code)).toEqual(name2); + + // REPLACE policy now fails due to a name collision + await expect(client.functionRestore(dump)).rejects.toThrow( + new RegExp(`Function ${name1}|${name2} already exists`), + ); + + // FLUSH policy succeeds, but deletes the second lib + expect( + await client.functionRestore( + dump, + FunctionRestorePolicy.FLUSH, + ), + ).toEqual("OK"); + expect(await client.functionList({ withCode: true })).toEqual( + flist, + ); + + // call restored functions + expect(await client.fcall(name1, [], ["meow", "woem"])).toEqual( + "meow", + ); + expect(await client.fcall(name2, [], ["meow", "woem"])).toEqual( + 2, + ); + } finally { + expect(await client.functionFlush()).toEqual("OK"); + client.close(); + } + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "function dump function restore in transaction %p", + async (protocol) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + + const config = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + ); + const client = await GlideClient.createClient(config); + expect(await client.functionFlush()).toEqual("OK"); + + try { + const name1 = "Foster"; + const name2 = "Dogster"; + // function returns first argument + const code = generateLuaLibCode( + name1, + new Map([[name2, "return args[1]"]]), + false, + ); + expect(await client.functionLoad(code)).toEqual(name1); + + // Verify functionDump + let transaction = new Transaction().functionDump(); + const result = await client.exec(transaction, Decoder.Bytes); + const data = result?.[0] as Buffer; + + // Verify functionRestore + transaction = new Transaction() + .functionRestore(data, FunctionRestorePolicy.REPLACE) + .fcall(name2, [], ["meow"]); + expect(await client.exec(transaction)).toEqual(["OK", "meow"]); + } finally { + expect(await client.functionFlush()).toEqual("OK"); + client.close(); + } + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "sort sortstore sort_store sortro sort_ro sortreadonly test_%p", + async (protocol) => { + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const setPrefix = "setKey" + uuidv4(); + const hashPrefix = "hashKey" + uuidv4(); + const list = uuidv4(); + const store = uuidv4(); + const names = ["Alice", "Bob", "Charlie", "Dave", "Eve"]; + const ages = ["30", "25", "35", "20", "40"]; + + for (let i = 0; i < ages.length; i++) { + const fieldValueList: HashDataType = [ + { field: "name", value: names[i] }, + { field: "age", value: ages[i] }, + ]; + expect( + await client.hset(setPrefix + (i + 1), fieldValueList), + ).toEqual(2); + } + + expect(await client.rpush(list, ["3", "1", "5", "4", "2"])).toEqual( + 5, + ); + + expect( + await client.sort(list, { + limit: { offset: 0, count: 2 }, + getPatterns: [setPrefix + "*->name"], + }), + ).toEqual(["Alice", "Bob"]); + + expect( + await client.sort(Buffer.from(list), { + limit: { offset: 0, count: 2 }, + getPatterns: [setPrefix + "*->name"], + orderBy: SortOrder.DESC, + }), + ).toEqual(["Eve", "Dave"]); + + expect( + await client.sort(list, { + limit: { offset: 0, count: 2 }, + byPattern: setPrefix + "*->age", + getPatterns: [setPrefix + "*->name", setPrefix + "*->age"], + orderBy: SortOrder.DESC, + }), + ).toEqual(["Eve", "40", "Charlie", "35"]); + + // test binary decoder + expect( + await client.sort(list, { + limit: { offset: 0, count: 2 }, + byPattern: setPrefix + "*->age", + getPatterns: [setPrefix + "*->name", setPrefix + "*->age"], + orderBy: SortOrder.DESC, + decoder: Decoder.Bytes, + }), + ).toEqual([ + Buffer.from("Eve"), + Buffer.from("40"), + Buffer.from("Charlie"), + Buffer.from("35"), + ]); + + // Non-existent key in the BY pattern will result in skipping the sorting operation + expect(await client.sort(list, { byPattern: "noSort" })).toEqual([ + "3", + "1", + "5", + "4", + "2", + ]); + + // Non-existent key in the GET pattern results in nulls + expect( + await client.sort(list, { + isAlpha: true, + getPatterns: ["missing"], + }), + ).toEqual([null, null, null, null, null]); + + // Missing key in the set + expect(await client.lpush(list, ["42"])).toEqual(6); + expect( + await client.sort(list, { + byPattern: setPrefix + "*->age", + getPatterns: [setPrefix + "*->name"], + }), + ).toEqual([null, "Dave", "Bob", "Alice", "Charlie", "Eve"]); + expect(await client.lpop(list)).toEqual("42"); + + // sort RO + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + expect( + await client.sortReadOnly(list, { + limit: { offset: 0, count: 2 }, + getPatterns: [setPrefix + "*->name"], + }), + ).toEqual(["Alice", "Bob"]); + + expect( + await client.sortReadOnly(list, { + limit: { offset: 0, count: 2 }, + getPatterns: [setPrefix + "*->name"], + orderBy: SortOrder.DESC, + decoder: Decoder.Bytes, + }), + ).toEqual([Buffer.from("Eve"), Buffer.from("Dave")]); + + expect( + await client.sortReadOnly(Buffer.from(list), { + limit: { offset: 0, count: 2 }, + byPattern: setPrefix + "*->age", + getPatterns: [ + setPrefix + "*->name", + setPrefix + "*->age", + ], + orderBy: SortOrder.DESC, + }), + ).toEqual(["Eve", "40", "Charlie", "35"]); + + // Non-existent key in the BY pattern will result in skipping the sorting operation + expect( + await client.sortReadOnly(list, { byPattern: "noSort" }), + ).toEqual(["3", "1", "5", "4", "2"]); + + // Non-existent key in the GET pattern results in nulls + expect( + await client.sortReadOnly(list, { + isAlpha: true, + getPatterns: ["missing"], + }), + ).toEqual([null, null, null, null, null]); + + // Missing key in the set + expect(await client.lpush(list, ["42"])).toEqual(6); + expect( + await client.sortReadOnly(list, { + byPattern: setPrefix + "*->age", + getPatterns: [setPrefix + "*->name"], + }), + ).toEqual([null, "Dave", "Bob", "Alice", "Charlie", "Eve"]); + expect(await client.lpop(list)).toEqual("42"); + } + + // SORT with STORE + expect( + await client.sortStore(list, store, { + limit: { offset: 0, count: -1 }, + byPattern: setPrefix + "*->age", + getPatterns: [setPrefix + "*->name"], + orderBy: SortOrder.ASC, + }), + ).toEqual(5); + expect(await client.lrange(store, 0, -1)).toEqual([ + "Dave", + "Bob", + "Alice", + "Charlie", + "Eve", + ]); + expect( + await client.sortStore(Buffer.from(list), store, { + byPattern: setPrefix + "*->age", + getPatterns: [setPrefix + "*->name"], + }), + ).toEqual(5); + expect(await client.lrange(store, 0, -1)).toEqual([ + "Dave", + "Bob", + "Alice", + "Charlie", + "Eve", + ]); + + // transaction test + const transaction = new Transaction() + .hset(hashPrefix + 1, [ + { field: "name", value: "Alice" }, + { field: "age", value: "30" }, + ]) + .hset(hashPrefix + 2, { + name: "Bob", + age: "25", + }) + .del([list]) + .lpush(list, ["2", "1"]) + .sort(list, { + byPattern: hashPrefix + "*->age", + getPatterns: [hashPrefix + "*->name"], + }) + .sort(list, { + byPattern: hashPrefix + "*->age", + getPatterns: [hashPrefix + "*->name"], + orderBy: SortOrder.DESC, + }) + .sortStore(list, store, { + byPattern: hashPrefix + "*->age", + getPatterns: [hashPrefix + "*->name"], + }) + .lrange(store, 0, -1) + .sortStore(list, store, { + byPattern: hashPrefix + "*->age", + getPatterns: [hashPrefix + "*->name"], + orderBy: SortOrder.DESC, + }) + .lrange(store, 0, -1); + + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + transaction + .sortReadOnly(list, { + byPattern: hashPrefix + "*->age", + getPatterns: [hashPrefix + "*->name"], + }) + .sortReadOnly(list, { + byPattern: hashPrefix + "*->age", + getPatterns: [hashPrefix + "*->name"], + orderBy: SortOrder.DESC, + }); + } + + const expectedResult = [ + 2, + 2, + 1, + 2, + ["Bob", "Alice"], + ["Alice", "Bob"], + 2, + ["Bob", "Alice"], + 2, + ["Alice", "Bob"], + ]; + + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + expectedResult.push(["Bob", "Alice"], ["Alice", "Bob"]); + } + + const result = await client.exec(transaction); + expect(result).toEqual(expectedResult); + + client.close(); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "randomKey test_%p", + async (protocol) => { + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key = uuidv4(); + + // setup: delete all keys in DB 0 and DB 1 + expect(await client.select(0)).toEqual("OK"); + expect(await client.flushdb(FlushMode.SYNC)).toEqual("OK"); + expect(await client.select(1)).toEqual("OK"); + expect(await client.flushdb(FlushMode.SYNC)).toEqual("OK"); + + // no keys exist so randomKey returns null + expect(await client.randomKey()).toBeNull(); + // set `key` in DB 1 + expect(await client.set(key, "foo")).toEqual("OK"); + // `key` should be the only key in the database + expect(await client.randomKey()).toEqual(key); + // test binary decoder + expect(await client.randomKey(Decoder.Bytes)).toEqual( + Buffer.from(key), + ); + + // switch back to DB 0 + expect(await client.select(0)).toEqual("OK"); + // DB 0 should still have no keys, so randomKey should still return null + expect(await client.randomKey()).toBeNull(); + + client.close(); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "watch test_%p", + async (protocol) => { + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const key3 = "{key}-3" + uuidv4(); + const key4 = "{key}-4" + uuidv4(); + const setFoobarTransaction = new Transaction(); + const setHelloTransaction = new Transaction(); + + // Returns null when a watched key is modified before it is executed in a transaction command. + // Transaction commands are not performed. + expect(await client.watch([key1, key2, key3])).toEqual("OK"); + expect(await client.set(key2, "hello")).toEqual("OK"); + setFoobarTransaction + .set(key1, "foobar") + .set(key2, "foobar") + .set(key3, "foobar"); + let results = await client.exec(setFoobarTransaction); + expect(results).toEqual(null); + // sanity check + expect(await client.get(key1)).toEqual(null); + expect(await client.get(key2)).toEqual("hello"); + expect(await client.get(key3)).toEqual(null); + + // Transaction executes command successfully with a read command on the watch key before + // transaction is executed. + expect(await client.watch([key1, key2, key3])).toEqual("OK"); + expect(await client.get(key2)).toEqual("hello"); + results = await client.exec(setFoobarTransaction); + expect(results).toEqual(["OK", "OK", "OK"]); + // sanity check + expect(await client.get(key1)).toEqual("foobar"); + expect(await client.get(key2)).toEqual("foobar"); + expect(await client.get(key3)).toEqual("foobar"); + + // Transaction executes command successfully with unmodified watched keys + expect(await client.watch([key1, Buffer.from(key2), key3])).toEqual( + "OK", + ); + results = await client.exec(setFoobarTransaction); + expect(results).toEqual(["OK", "OK", "OK"]); + // sanity check + expect(await client.get(key1)).toEqual("foobar"); + expect(await client.get(key2)).toEqual("foobar"); + expect(await client.get(key3)).toEqual("foobar"); + + // Transaction executes command successfully with a modified watched key but is not in the + // transaction. + expect(await client.watch([key4])).toEqual("OK"); + setHelloTransaction + .set(key1, "hello") + .set(key2, "hello") + .set(key3, "hello"); + results = await client.exec(setHelloTransaction); + expect(results).toEqual(["OK", "OK", "OK"]); + // sanity check + expect(await client.get(key1)).toEqual("hello"); + expect(await client.get(key2)).toEqual("hello"); + expect(await client.get(key3)).toEqual("hello"); + + // WATCH can not have an empty String array parameter + await expect(client.watch([])).rejects.toThrow(RequestError); + + client.close(); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "unwatch test_%p", + async (protocol) => { + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + + const setFoobarTransaction = new Transaction(); + + // UNWATCH returns OK when there no watched keys + expect(await client.unwatch()).toEqual("OK"); + + // Transaction executes successfully after modifying a watched key then calling UNWATCH + expect(await client.watch([key1, key2])).toEqual("OK"); + expect(await client.set(key2, "hello")).toEqual("OK"); + expect(await client.unwatch()).toEqual("OK"); + setFoobarTransaction.set(key1, "foobar").set(key2, "foobar"); + const results = await client.exec(setFoobarTransaction); + expect(results).toEqual(["OK", "OK"]); + // sanity check + expect(await client.get(key1)).toEqual("foobar"); + expect(await client.get(key2)).toEqual("foobar"); + + client.close(); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "xinfo stream transaction test_%p", + async (protocol) => { + const client = await GlideClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key = uuidv4(); + + const transaction = new Transaction(); + transaction.xadd(key, [["field1", "value1"]], { id: "0-1" }); + transaction.xinfoStream(key); + transaction.xinfoStream(key, true); + const result = await client.exec(transaction); + expect(result).not.toBeNull(); + + const versionLessThan7 = + cluster.checkIfServerVersionLessThan("7.0.0"); + + const expectedXinfoStreamResult = { + length: 1, + "radix-tree-keys": 1, + "radix-tree-nodes": 2, + "last-generated-id": "0-1", + groups: 0, + "first-entry": ["0-1", ["field1", "value1"]], + "last-entry": ["0-1", ["field1", "value1"]], + "max-deleted-entry-id": versionLessThan7 ? undefined : "0-0", + "entries-added": versionLessThan7 ? undefined : 1, + "recorded-first-entry-id": versionLessThan7 ? undefined : "0-1", + }; + + const expectedXinfoStreamFullResult = { + length: 1, + "radix-tree-keys": 1, + "radix-tree-nodes": 2, + "last-generated-id": "0-1", + entries: [["0-1", ["field1", "value1"]]], + groups: [], + "max-deleted-entry-id": versionLessThan7 ? undefined : "0-0", + "entries-added": versionLessThan7 ? undefined : 1, + "recorded-first-entry-id": versionLessThan7 ? undefined : "0-1", + }; + + if (result != null) { + expect(result[0]).toEqual("0-1"); // xadd + expect(result[1]).toEqual(expectedXinfoStreamResult); + expect(result[2]).toEqual(expectedXinfoStreamFullResult); + } + + client.close(); + }, + TIMEOUT, + ); + + runBaseTests({ + init: async (protocol, configOverrides) => { + const config = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + configOverrides, + ); + + testsFailed += 1; + client = await GlideClient.createClient(config); + return { client, cluster }; + }, + close: (testSucceeded: boolean) => { + if (testSucceeded) { + testsFailed -= 1; + } + }, + timeout: TIMEOUT, + }); +}); diff --git a/node/tests/RedisClientInternals.test.ts b/node/tests/GlideClientInternals.test.ts similarity index 96% rename from node/tests/RedisClientInternals.test.ts rename to node/tests/GlideClientInternals.test.ts index 888b47c374..b2025b0cc8 100644 --- a/node/tests/RedisClientInternals.test.ts +++ b/node/tests/GlideClientInternals.test.ts @@ -21,23 +21,25 @@ import { Reader } from "protobufjs"; import { BaseClientConfiguration, ClosingError, - ClusterClientConfiguration, + ClusterTransaction, + Decoder, GlideClient, GlideClientConfiguration, GlideClusterClient, + GlideClusterClientConfiguration, InfoOptions, Logger, RequestError, ReturnType, SlotKeyTypes, - Transaction, + TimeUnit, } from ".."; import { command_request, connection_request, response, } from "../src/ProtobufMessage"; -import { convertStringArrayToBuffer, intoString } from "./TestUtilities"; +import { convertStringArrayToBuffer } from "./TestUtilities"; const { RequestType, CommandRequest } = command_request; beforeAll(() => { @@ -124,7 +126,9 @@ function sendResponse( function getConnectionAndSocket( checkRequest?: (request: connection_request.ConnectionRequest) => boolean, - connectionOptions?: ClusterClientConfiguration | GlideClientConfiguration, + connectionOptions?: + | GlideClusterClientConfiguration + | GlideClientConfiguration, isCluster?: boolean, ): Promise<{ socket: net.Socket; @@ -306,8 +310,8 @@ describe("SocketConnectionInternals", () => { }, ); }); - const result = await connection.get("foo"); - expect(intoString(result)).toEqual(intoString(expected)); + const result = await connection.get("foo", Decoder.String); + expect(result).toEqual(expected); }); }; @@ -376,13 +380,15 @@ describe("SocketConnectionInternals", () => { sendResponse(socket, ResponseType.OK, request.callbackIdx); }); - const transaction = new Transaction(); + const transaction = new ClusterTransaction(); transaction.set("key", "value"); const slotKey: SlotKeyTypes = { type: "primarySlotKey", key: "key", }; - const result = await connection.exec(transaction, slotKey); + const result = await connection.exec(transaction, { + route: slotKey, + }); expect(result).toBe("OK"); }); }); @@ -408,12 +414,12 @@ describe("SocketConnectionInternals", () => { value: "# Server", }); }); - const transaction = new Transaction(); + const transaction = new ClusterTransaction(); transaction.info([InfoOptions.Server]); - const result = await connection.exec(transaction, "randomNode"); - expect(intoString(result)).toEqual( - expect.stringContaining("# Server"), - ); + const result = await connection.exec(transaction, { + route: "randomNode", + }); + expect(result).toEqual(expect.stringContaining("# Server")); }); }); @@ -539,7 +545,7 @@ describe("SocketConnectionInternals", () => { const request1 = connection.set("foo", "bar", { conditionalSet: "onlyIfExists", returnOldValue: true, - expiry: { type: "seconds", count: 10 }, + expiry: { type: TimeUnit.Seconds, count: 10 }, }); expect(await request1).toMatch("OK"); @@ -699,14 +705,13 @@ describe("SocketConnectionInternals", () => { }); const result1 = await connection.customCommand( ["SET", "foo", "bar"], - route1, + { route: route1 }, ); expect(result1).toBeNull(); - const result2 = await connection.customCommand( - ["GET", "foo"], - route2, - ); + const result2 = await connection.customCommand(["GET", "foo"], { + route: route2, + }); expect(result2).toBeNull(); }); }); diff --git a/node/tests/GlideClusterClient.test.ts b/node/tests/GlideClusterClient.test.ts new file mode 100644 index 0000000000..d938d20c2b --- /dev/null +++ b/node/tests/GlideClusterClient.test.ts @@ -0,0 +1,1772 @@ +/** + * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + */ + +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + it, +} from "@jest/globals"; +import { gte } from "semver"; +import { v4 as uuidv4 } from "uuid"; +import { + BitwiseOperation, + ClusterTransaction, + Decoder, + FunctionListResponse, + GlideClusterClient, + InfoOptions, + ListDirection, + ProtocolVersion, + RequestError, + ReturnType, + Routes, + ScoreFilter, + SlotKeyTypes, +} from ".."; +import { RedisCluster } from "../../utils/TestUtils.js"; +import { + FlushMode, + FunctionRestorePolicy, + FunctionStatsSingleResponse, + GeoUnit, + SortOrder, +} from "../build-ts/src/Commands"; +import { runBaseTests } from "./SharedTests"; +import { + checkClusterResponse, + checkFunctionListResponse, + checkFunctionStatsResponse, + createLuaLibWithLongRunningFunction, + flushAndCloseClient, + generateLuaLibCode, + getClientConfigurationOption, + getFirstResult, + intoArray, + intoString, + parseCommandLineArgs, + parseEndpoints, + transactionTest, + validateTransactionResponse, + waitForNotBusy, +} from "./TestUtilities"; + +const TIMEOUT = 50000; + +describe("GlideClusterClient", () => { + let testsFailed = 0; + let cluster: RedisCluster; + let client: GlideClusterClient; + beforeAll(async () => { + const clusterAddresses = parseCommandLineArgs()["cluster-endpoints"]; + // Connect to cluster or create a new one based on the parsed addresses + cluster = clusterAddresses + ? await RedisCluster.initFromExistingCluster( + parseEndpoints(clusterAddresses), + ) + : // setting replicaCount to 1 to facilitate tests routed to replicas + await RedisCluster.createCluster(true, 3, 1); + }, 20000); + + afterEach(async () => { + await flushAndCloseClient(true, cluster.getAddresses(), client); + }); + + afterAll(async () => { + if (testsFailed === 0) { + await cluster.close(); + } + }); + + runBaseTests({ + init: async (protocol, configOverrides) => { + const config = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + configOverrides, + ); + + testsFailed += 1; + client = await GlideClusterClient.createClient(config); + return { + client, + cluster, + }; + }, + close: (testSucceeded: boolean) => { + if (testSucceeded) { + testsFailed -= 1; + } + }, + timeout: TIMEOUT, + }); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `info with server and replication_%p`, + async (protocol) => { + client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const info_server = getFirstResult( + await client.info({ sections: [InfoOptions.Server] }), + ); + expect(info_server).toEqual(expect.stringContaining("# Server")); + + const infoReplicationValues = Object.values( + await client.info({ sections: [InfoOptions.Replication] }), + ); + + const replicationInfo = intoArray(infoReplicationValues); + + for (const item of replicationInfo) { + expect(item).toContain("role:master"); + expect(item).toContain("# Replication"); + } + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `info with server and randomNode route_%p`, + async (protocol) => { + client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const result = await client.info({ + sections: [InfoOptions.Server], + route: "randomNode", + }); + expect(result).toEqual(expect.stringContaining("# Server")); + expect(result).toEqual(expect.not.stringContaining("# Errorstats")); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `route by address reaches correct node_%p`, + async (protocol) => { + // returns the line that contains the word "myself", up to that point. This is done because the values after it might change with time. + const cleanResult = (value: string) => { + return ( + value + .split("\n") + .find((line: string) => line.includes("myself")) + ?.split("myself")[0] ?? "" + ); + }; + + client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const result = cleanResult( + intoString( + await client.customCommand(["cluster", "nodes"], { + route: "randomNode", + }), + ), + ); + + // check that routing without explicit port works + const host = result.split(" ")[1].split("@")[0] ?? ""; + + if (!host) { + throw new Error("No host could be parsed"); + } + + const secondResult = cleanResult( + intoString( + await client.customCommand(["cluster", "nodes"], { + route: { + type: "routeByAddress", + host, + }, + }), + ), + ); + + expect(result).toEqual(secondResult); + + const [host2, port] = host.split(":"); + + // check that routing with explicit port works + const thirdResult = cleanResult( + intoString( + await client.customCommand(["cluster", "nodes"], { + route: { + type: "routeByAddress", + host: host2, + port: Number(port), + }, + }), + ), + ); + + expect(result).toEqual(thirdResult); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `fail routing by address if no port is provided_%p`, + async (protocol) => { + client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + await expect( + client.info({ + route: { + type: "routeByAddress", + host: "foo", + }, + }), + ).rejects.toThrowError(RequestError); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `dump and restore custom command_%p`, + async (protocol) => { + client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key = "key"; + const value = "value"; + const valueEncoded = Buffer.from(value); + expect(await client.set(key, value)).toEqual("OK"); + // Since DUMP gets binary results, we cannot use the default decoder (string) here, so we expected to get an error. + await expect(client.customCommand(["DUMP", key])).rejects.toThrow( + "invalid utf-8 sequence of 1 bytes from index 9", + ); + + const dumpResult = await client.customCommand(["DUMP", key], { + decoder: Decoder.Bytes, + }); + + expect(await client.del([key])).toEqual(1); + + if (dumpResult instanceof Buffer) { + // check the delete + expect(await client.get(key)).toEqual(null); + expect( + await client.customCommand( + ["RESTORE", key, "0", dumpResult], + { decoder: Decoder.Bytes }, + ), + ).toEqual("OK"); + // check the restore + expect(await client.get(key)).toEqual(value); + expect(await client.get(key, Decoder.Bytes)).toEqual( + valueEncoded, + ); + } + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `config get and config set transactions test_%p`, + async (protocol) => { + client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const transaction = new ClusterTransaction(); + transaction.configSet({ timeout: "1000" }); + transaction.configGet(["timeout"]); + const result = await client.exec(transaction); + expect(intoString(result)).toEqual( + intoString(["OK", { timeout: "1000" }]), + ); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `can send transactions_%p`, + async (protocol) => { + client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const transaction = new ClusterTransaction(); + + const expectedRes = await transactionTest( + transaction, + cluster.getVersion(), + ); + + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + transaction.publish("message", "key", true); + expectedRes.push(['publish("message", "key", true)', 0]); + + transaction.pubsubShardChannels(); + expectedRes.push(["pubsubShardChannels()", []]); + transaction.pubsubShardNumSub(); + expectedRes.push(["pubsubShardNumSub()", {}]); + } + + const result = await client.exec(transaction); + validateTransactionResponse(result, expectedRes); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `can return null on WATCH transaction failures_%p`, + async (protocol) => { + const client1 = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const client2 = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const transaction = new ClusterTransaction(); + transaction.get("key"); + const result1 = await client1.watch(["key"]); + expect(result1).toEqual("OK"); + + const result2 = await client2.set("key", "foo"); + expect(result2).toEqual("OK"); + + const result3 = await client1.exec(transaction); + expect(result3).toBeNull(); + + client1.close(); + client2.close(); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `echo with all nodes routing_%p`, + async (protocol) => { + client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const message = uuidv4(); + const echoDict = await client.echo(message, { route: "allNodes" }); + + expect(typeof echoDict).toBe("object"); + expect(intoArray(echoDict)).toEqual( + expect.arrayContaining(intoArray([message])), + ); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `check that multi key command returns a cross slot error`, + async (protocol) => { + const client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const promises: Promise[] = [ + client.blpop(["abc", "zxy", "lkn"], 0.1), + client.rename("abc", "zxy"), + client.msetnx({ abc: "xyz", def: "abc", hij: "def" }), + client.brpop(["abc", "zxy", "lkn"], 0.1), + client.bitop(BitwiseOperation.AND, "abc", ["zxy", "lkn"]), + client.smove("abc", "zxy", "value"), + client.renamenx("abc", "zxy"), + client.sinter(["abc", "zxy", "lkn"]), + client.sinterstore("abc", ["zxy", "lkn"]), + client.zinterstore("abc", ["zxy", "lkn"]), + client.zunionstore("abc", ["zxy", "lkn"]), + client.sunionstore("abc", ["zxy", "lkn"]), + client.sunion(["abc", "zxy", "lkn"]), + client.pfcount(["abc", "zxy", "lkn"]), + client.pfmerge("abc", ["def", "ghi"]), + client.sdiff(["abc", "zxy", "lkn"]), + client.sdiffstore("abc", ["zxy", "lkn"]), + client.sortStore("abc", "zyx"), + client.sortStore("abc", "zyx", { isAlpha: true }), + client.lmpop(["abc", "def"], ListDirection.LEFT, 1), + client.blmpop(["abc", "def"], ListDirection.RIGHT, 0.1, 1), + client.bzpopmax(["abc", "def"], 0.5), + client.bzpopmin(["abc", "def"], 0.5), + client.xread({ abc: "0-0", zxy: "0-0", lkn: "0-0" }), + client.xreadgroup("_", "_", { abc: ">", zxy: ">", lkn: ">" }), + ]; + + if (gte(cluster.getVersion(), "6.2.0")) { + promises.push( + client.blmove( + "abc", + "def", + ListDirection.LEFT, + ListDirection.LEFT, + 0.2, + ), + client.zdiff(["abc", "zxy", "lkn"]), + client.zdiffWithScores(["abc", "zxy", "lkn"]), + client.zdiffstore("abc", ["zxy", "lkn"]), + client.copy("abc", "zxy", true), + client.geosearchstore( + "abc", + "zxy", + { member: "_" }, + { radius: 5, unit: GeoUnit.METERS }, + ), + client.zrangeStore("abc", "zyx", { start: 0, stop: -1 }), + client.zinter(["abc", "zxy", "lkn"]), + client.zinterWithScores(["abc", "zxy", "lkn"]), + client.zunion(["abc", "zxy", "lkn"]), + client.zunionWithScores(["abc", "zxy", "lkn"]), + ); + } + + if (gte(cluster.getVersion(), "7.0.0")) { + promises.push( + client.sintercard(["abc", "zxy", "lkn"]), + client.zintercard(["abc", "zxy", "lkn"]), + client.zmpop(["abc", "zxy", "lkn"], ScoreFilter.MAX), + client.bzmpop(["abc", "zxy", "lkn"], ScoreFilter.MAX, 0.1), + client.lcs("abc", "xyz"), + client.lcsLen("abc", "xyz"), + client.lcsIdx("abc", "xyz"), + ); + } + + for (const promise of promises) { + await expect(promise).rejects.toThrowError(/crossslot/i); + } + + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `check that multi key command routed to multiple nodes`, + async (protocol) => { + const client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + await client.exists(["abc", "zxy", "lkn"]); + await client.unlink(["abc", "zxy", "lkn"]); + await client.del(["abc", "zxy", "lkn"]); + await client.mget(["abc", "zxy", "lkn"]); + await client.mset({ abc: "1", zxy: "2", lkn: "3" }); + await client.touch(["abc", "zxy", "lkn"]); + await client.watch(["ghi", "zxy", "lkn"]); + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "object freq transaction test_%p", + async (protocol) => { + const client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key = uuidv4(); + const maxmemoryPolicyKey = "maxmemory-policy"; + const config = await client.configGet([maxmemoryPolicyKey]); + const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); + + try { + const transaction = new ClusterTransaction(); + transaction.configSet({ + [maxmemoryPolicyKey]: "allkeys-lfu", + }); + transaction.set(key, "foo"); + transaction.objectFreq(key); + + const response = await client.exec(transaction); + expect(response).not.toBeNull(); + + if (response != null) { + expect(response.length).toEqual(3); + expect(response[0]).toEqual("OK"); + expect(response[1]).toEqual("OK"); + expect(response[2]).toBeGreaterThanOrEqual(0); + } + } finally { + expect( + await client.configSet({ + [maxmemoryPolicyKey]: maxmemoryPolicy, + }), + ).toEqual("OK"); + } + + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "object idletime transaction test_%p", + async (protocol) => { + const client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key = uuidv4(); + const maxmemoryPolicyKey = "maxmemory-policy"; + const config = await client.configGet([maxmemoryPolicyKey]); + const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); + + try { + const transaction = new ClusterTransaction(); + transaction.configSet({ + // OBJECT IDLETIME requires a non-LFU maxmemory-policy + [maxmemoryPolicyKey]: "allkeys-random", + }); + transaction.set(key, "foo"); + transaction.objectIdletime(key); + + const response = await client.exec(transaction); + expect(response).not.toBeNull(); + + if (response != null) { + expect(response.length).toEqual(3); + // transaction.configSet({[maxmemoryPolicyKey]: "allkeys-random"}); + expect(response[0]).toEqual("OK"); + // transaction.set(key, "foo"); + expect(response[1]).toEqual("OK"); + // transaction.objectIdletime(key); + expect(response[2]).toBeGreaterThanOrEqual(0); + } + } finally { + expect( + await client.configSet({ + [maxmemoryPolicyKey]: maxmemoryPolicy, + }), + ).toEqual("OK"); + } + + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "object refcount transaction test_%p", + async (protocol) => { + const client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key = uuidv4(); + const transaction = new ClusterTransaction(); + transaction.set(key, "foo"); + transaction.objectRefcount(key); + + const response = await client.exec(transaction); + expect(response).not.toBeNull(); + + if (response != null) { + expect(response.length).toEqual(2); + expect(response[0]).toEqual("OK"); // transaction.set(key, "foo"); + expect(response[1]).toBeGreaterThanOrEqual(1); // transaction.objectRefcount(key); + } + + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `lolwut test_%p`, + async (protocol) => { + client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + // test with multi-node route + const result1 = await client.lolwut({ route: "allNodes" }); + expect(intoString(result1)).toEqual( + expect.stringContaining("Redis ver. "), + ); + + const result2 = await client.lolwut({ + version: 2, + parameters: [10, 20], + route: "allNodes", + }); + expect(intoString(result2)).toEqual( + expect.stringContaining("Redis ver. "), + ); + + // test with single-node route + const result3 = await client.lolwut({ route: "randomNode" }); + expect(intoString(result3)).toEqual( + expect.stringContaining("Redis ver. "), + ); + + const result4 = await client.lolwut({ + version: 2, + parameters: [10, 20], + route: "randomNode", + }); + expect(intoString(result4)).toEqual( + expect.stringContaining("Redis ver. "), + ); + + // transaction tests + const transaction = new ClusterTransaction(); + transaction.lolwut(); + transaction.lolwut({ version: 5 }); + transaction.lolwut({ parameters: [1, 2] }); + transaction.lolwut({ version: 6, parameters: [42] }); + const results = await client.exec(transaction); + + if (results) { + for (const element of results) { + expect(intoString(element)).toEqual( + expect.stringContaining("Redis ver. "), + ); + } + } else { + throw new Error("Invalid LOLWUT transaction test results."); + } + + client.close(); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "copy test_%p", + async (protocol) => { + const client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + + const source = `{key}-${uuidv4()}`; + const destination = `{key}-${uuidv4()}`; + const value1 = uuidv4(); + const value2 = uuidv4(); + + // neither key exists + expect(await client.copy(source, destination, true)).toEqual(false); + expect(await client.copy(Buffer.from(source), destination)).toEqual( + false, + ); + + // source exists, destination does not + expect(await client.set(source, value1)).toEqual("OK"); + expect( + await client.copy(source, Buffer.from(destination), false), + ).toEqual(true); + expect(await client.get(destination)).toEqual(value1); + + // new value for source key + expect(await client.set(source, value2)).toEqual("OK"); + + // both exists, no REPLACE + expect( + await client.copy( + Buffer.from(source), + Buffer.from(destination), + ), + ).toEqual(false); + expect(await client.copy(source, destination, false)).toEqual( + false, + ); + expect(await client.get(destination)).toEqual(value1); + + // both exists, with REPLACE + expect( + await client.copy(source, Buffer.from(destination), true), + ).toEqual(true); + expect(await client.get(destination)).toEqual(value2); + + //transaction tests + const transaction = new ClusterTransaction(); + transaction.set(source, value1); + transaction.copy(source, destination, true); + transaction.get(destination); + const results = await client.exec(transaction); + + expect(results).toEqual(["OK", true, value1]); + + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "flushdb flushall dbsize test_%p", + async (protocol) => { + const client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + expect(await client.dbsize()).toBeGreaterThanOrEqual(0); + expect(await client.set(uuidv4(), uuidv4())).toEqual("OK"); + expect(await client.dbsize()).toBeGreaterThan(0); + + expect(await client.flushall()).toEqual("OK"); + expect(await client.dbsize()).toEqual(0); + + expect(await client.set(uuidv4(), uuidv4())).toEqual("OK"); + expect(await client.dbsize()).toEqual(1); + expect(await client.flushdb({ mode: FlushMode.ASYNC })).toEqual( + "OK", + ); + expect(await client.dbsize()).toEqual(0); + + expect(await client.set(uuidv4(), uuidv4())).toEqual("OK"); + expect(await client.dbsize()).toEqual(1); + expect(await client.flushdb({ mode: FlushMode.SYNC })).toEqual( + "OK", + ); + expect(await client.dbsize()).toEqual(0); + + client.close(); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "sort sortstore sort_store sortro sort_ro sortreadonly test_%p", + async (protocol) => { + const client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + const key1 = "{sort}" + uuidv4(); + const key2 = "{sort}" + uuidv4(); + const key3 = "{sort}" + uuidv4(); + const key4 = "{sort}" + uuidv4(); + const key5 = "{sort}" + uuidv4(); + + expect(await client.sort(key3)).toEqual([]); + expect(await client.lpush(key1, ["2", "1", "4", "3"])).toEqual(4); + expect(await client.sort(Buffer.from(key1))).toEqual([ + "1", + "2", + "3", + "4", + ]); + // test binary decoder + expect(await client.sort(key1, { decoder: Decoder.Bytes })).toEqual( + [ + Buffer.from("1"), + Buffer.from("2"), + Buffer.from("3"), + Buffer.from("4"), + ], + ); + + // sort RO + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + expect(await client.sortReadOnly(key3)).toEqual([]); + expect(await client.sortReadOnly(Buffer.from(key3))).toEqual( + [], + ); + // test binary decoder + expect( + await client.sortReadOnly(key1, { decoder: Decoder.Bytes }), + ).toEqual([ + Buffer.from("1"), + Buffer.from("2"), + Buffer.from("3"), + Buffer.from("4"), + ]); + } + + // sort with store + expect(await client.sortStore(key1, key2)).toEqual(4); + expect( + await client.sortStore(Buffer.from(key1), Buffer.from(key2)), + ).toEqual(4); + expect(await client.lrange(key2, 0, -1)).toEqual([ + "1", + "2", + "3", + "4", + ]); + + // SORT with strings require ALPHA + expect( + await client.rpush(key3, ["2", "1", "a", "x", "c", "4", "3"]), + ).toEqual(7); + await expect(client.sort(key3)).rejects.toThrow(RequestError); + expect(await client.sort(key3, { isAlpha: true })).toEqual([ + "1", + "2", + "3", + "4", + "a", + "c", + "x", + ]); + + // check transaction and options + const transaction = new ClusterTransaction() + .lpush(key4, ["3", "1", "2"]) + .sort(key4, { + orderBy: SortOrder.DESC, + limit: { count: 2, offset: 0 }, + }) + .sortStore(key4, key5, { + orderBy: SortOrder.ASC, + limit: { count: 100, offset: 1 }, + }) + .lrange(key5, 0, -1); + + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + transaction.sortReadOnly(key4, { + orderBy: SortOrder.DESC, + limit: { count: 2, offset: 0 }, + }); + } + + const result = await client.exec(transaction); + const expectedResult = [3, ["3", "2"], 2, ["2", "3"]]; + + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + expectedResult.push(["3", "2"]); + } + + expect(result).toEqual(expectedResult); + + client.close(); + }, + ); + + describe.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "Protocol is RESP2 = %s", + (protocol) => { + describe.each([true, false])( + "Single node route = %s", + (singleNodeRoute) => { + it( + "function load function list function stats", + async () => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) + return; + + const client = + await GlideClusterClient.createClient( + getClientConfigurationOption( + cluster.getAddresses(), + protocol, + ), + ); + + try { + const libName = + "mylib1C" + uuidv4().replaceAll("-", ""); + const funcName = + "myfunc1c" + uuidv4().replaceAll("-", ""); + const code = generateLuaLibCode( + libName, + new Map([[funcName, "return args[1]"]]), + true, + ); + const route: Routes = singleNodeRoute + ? { type: "primarySlotKey", key: "1" } + : "allPrimaries"; + + let functionList = await client.functionList({ + libNamePattern: libName, + route: route, + }); + checkClusterResponse( + functionList as object, + singleNodeRoute, + (value) => expect(value).toEqual([]), + ); + + let functionStats = await client.functionStats({ + route: route, + }); + checkClusterResponse( + functionStats as object, + singleNodeRoute, + (value) => + checkFunctionStatsResponse( + value as FunctionStatsSingleResponse, + [], + 0, + 0, + ), + ); + + // load the library + expect(await client.functionLoad(code)).toEqual( + libName, + ); + + functionList = await client.functionList({ + libNamePattern: libName, + route: route, + }); + let expectedDescription = new Map< + string, + string | null + >([[funcName, null]]); + let expectedFlags = new Map([ + [funcName, ["no-writes"]], + ]); + + checkClusterResponse( + functionList, + singleNodeRoute, + (value) => + checkFunctionListResponse( + value as FunctionListResponse, + libName, + expectedDescription, + expectedFlags, + ), + ); + functionStats = await client.functionStats({ + route: route, + }); + checkClusterResponse( + functionStats as object, + singleNodeRoute, + (value) => + checkFunctionStatsResponse( + value as FunctionStatsSingleResponse, + [], + 1, + 1, + ), + ); + + // call functions from that library to confirm that it works + let fcall = await client.fcallWithRoute( + funcName, + ["one", "two"], + { route: route }, + ); + checkClusterResponse( + fcall as object, + singleNodeRoute, + (value) => expect(value).toEqual("one"), + ); + fcall = await client.fcallReadonlyWithRoute( + funcName, + ["one", "two"], + { route: route }, + ); + checkClusterResponse( + fcall as object, + singleNodeRoute, + (value) => expect(value).toEqual("one"), + ); + + // re-load library without replace + await expect( + client.functionLoad(code), + ).rejects.toThrow( + `Library '${libName}' already exists`, + ); + + // re-load library with replace + expect( + await client.functionLoad(code, { + replace: true, + }), + ).toEqual(libName); + + // overwrite lib with new code + const func2Name = + "myfunc2c" + uuidv4().replaceAll("-", ""); + const newCode = generateLuaLibCode( + libName, + new Map([ + [funcName, "return args[1]"], + [func2Name, "return #args"], + ]), + true, + ); + expect( + await client.functionLoad(newCode, { + replace: true, + }), + ).toEqual(libName); + + functionList = await client.functionList({ + libNamePattern: libName, + withCode: true, + route: route, + }); + expectedDescription = new Map< + string, + string | null + >([ + [funcName, null], + [func2Name, null], + ]); + expectedFlags = new Map([ + [funcName, ["no-writes"]], + [func2Name, ["no-writes"]], + ]); + + checkClusterResponse( + functionList, + singleNodeRoute, + (value) => + checkFunctionListResponse( + value as FunctionListResponse, + libName, + expectedDescription, + expectedFlags, + newCode, + ), + ); + functionStats = await client.functionStats({ + route: route, + }); + checkClusterResponse( + functionStats as object, + singleNodeRoute, + (value) => + checkFunctionStatsResponse( + value as FunctionStatsSingleResponse, + [], + 1, + 2, + ), + ); + + fcall = await client.fcallWithRoute( + func2Name, + ["one", "two"], + { route: route }, + ); + checkClusterResponse( + fcall as object, + singleNodeRoute, + (value) => expect(value).toEqual(2), + ); + + fcall = await client.fcallReadonlyWithRoute( + func2Name, + ["one", "two"], + { route: route }, + ); + checkClusterResponse( + fcall as object, + singleNodeRoute, + (value) => expect(value).toEqual(2), + ); + } finally { + expect(await client.functionFlush()).toEqual( + "OK", + ); + client.close(); + } + }, + TIMEOUT, + ); + it( + "function flush", + async () => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) + return; + + const client = + await GlideClusterClient.createClient( + getClientConfigurationOption( + cluster.getAddresses(), + protocol, + ), + ); + + try { + const libName = + "mylib1C" + uuidv4().replaceAll("-", ""); + const funcName = + "myfunc1c" + uuidv4().replaceAll("-", ""); + const code = generateLuaLibCode( + libName, + new Map([[funcName, "return args[1]"]]), + true, + ); + const route: Routes = singleNodeRoute + ? { type: "primarySlotKey", key: "1" } + : "allPrimaries"; + + const functionList1 = await client.functionList( + { route: route }, + ); + checkClusterResponse( + functionList1 as object, + singleNodeRoute, + (value) => expect(value).toEqual([]), + ); + + // load the library + expect( + await client.functionLoad(code, { + route: route, + }), + ).toEqual(libName); + + // flush functions + expect( + await client.functionFlush({ + mode: FlushMode.SYNC, + route: route, + }), + ).toEqual("OK"); + expect( + await client.functionFlush({ + mode: FlushMode.ASYNC, + route: route, + }), + ).toEqual("OK"); + + const functionList2 = + await client.functionList(); + checkClusterResponse( + functionList2 as object, + singleNodeRoute, + (value) => expect(value).toEqual([]), + ); + + // Attempt to re-load library without overwriting to ensure FLUSH was effective + expect( + await client.functionLoad(code, { + route: route, + }), + ).toEqual(libName); + } finally { + expect(await client.functionFlush()).toEqual( + "OK", + ); + client.close(); + } + }, + TIMEOUT, + ); + it( + "function delete", + async () => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) + return; + + const client = + await GlideClusterClient.createClient( + getClientConfigurationOption( + cluster.getAddresses(), + protocol, + ), + ); + + try { + const libName = + "mylib1C" + uuidv4().replaceAll("-", ""); + const funcName = + "myfunc1c" + uuidv4().replaceAll("-", ""); + const code = generateLuaLibCode( + libName, + new Map([[funcName, "return args[1]"]]), + true, + ); + const route: Routes = singleNodeRoute + ? { type: "primarySlotKey", key: "1" } + : "allPrimaries"; + let functionList = await client.functionList({ + route: route, + }); + checkClusterResponse( + functionList as object, + singleNodeRoute, + (value) => expect(value).toEqual([]), + ); + // load the library + expect( + await client.functionLoad(code, { + route: route, + }), + ).toEqual(libName); + + // Delete the function + expect( + await client.functionDelete(libName, route), + ).toEqual("OK"); + + functionList = await client.functionList({ + libNamePattern: libName, + withCode: true, + route: route, + }); + checkClusterResponse( + functionList as object, + singleNodeRoute, + (value) => expect(value).toEqual([]), + ); + + // Delete a non-existing library + await expect( + client.functionDelete(libName, route), + ).rejects.toThrow(`Library not found`); + } finally { + expect(await client.functionFlush()).toEqual( + "OK", + ); + client.close(); + } + }, + TIMEOUT, + ); + it( + "function kill with route", + async () => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) + return; + + const config = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + { requestTimeout: 10000 }, + ); + const client = + await GlideClusterClient.createClient(config); + const testClient = + await GlideClusterClient.createClient(config); + + try { + const libName = + "function_kill_no_write_with_route_" + + singleNodeRoute; + const funcName = + "deadlock_with_route_" + singleNodeRoute; + const code = + createLuaLibWithLongRunningFunction( + libName, + funcName, + 6, + true, + ); + const route: Routes = singleNodeRoute + ? { type: "primarySlotKey", key: "1" } + : "allPrimaries"; + expect(await client.functionFlush()).toEqual( + "OK", + ); + + // nothing to kill + await expect( + client.functionKill(route), + ).rejects.toThrow(/notbusy/i); + + // load the lib + expect( + await client.functionLoad(code, { + replace: true, + route: route, + }), + ).toEqual(libName); + + try { + // call the function without await + const promise = testClient + .fcallWithRoute(funcName, [], { + route: route, + }) + .catch((e) => + expect( + (e as Error).message, + ).toContain("Script killed"), + ); + + let killed = false; + let timeout = 4000; + await new Promise((resolve) => + setTimeout(resolve, 1000), + ); + + while (timeout >= 0) { + try { + expect( + await client.functionKill( + route, + ), + ).toEqual("OK"); + killed = true; + break; + } catch { + // do nothing + } + + await new Promise((resolve) => + setTimeout(resolve, 500), + ); + timeout -= 500; + } + + expect(killed).toBeTruthy(); + await promise; + } finally { + await waitForNotBusy(client); + } + } finally { + expect(await client.functionFlush()).toEqual( + "OK", + ); + client.close(); + testClient.close(); + } + }, + TIMEOUT, + ); + + it("function dump function restore %p", async () => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) + return; + + const config = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + ); + const client = + await GlideClusterClient.createClient(config); + const route: Routes = singleNodeRoute + ? { type: "primarySlotKey", key: "1" } + : "allPrimaries"; + expect( + await client.functionFlush({ + mode: FlushMode.SYNC, + route: route, + }), + ).toEqual("OK"); + + try { + // dumping an empty lib + let response = await client.functionDump(route); + + if (singleNodeRoute) { + expect(response.byteLength).toBeGreaterThan(0); + } else { + Object.values(response).forEach((d: Buffer) => + expect(d.byteLength).toBeGreaterThan(0), + ); + } + + const name1 = "Foster"; + const name2 = "Dogster"; + // function $name1 returns first argument + // function $name2 returns argument array len + let code = generateLuaLibCode( + name1, + new Map([ + [name1, "return args[1]"], + [name2, "return #args"], + ]), + false, + ); + expect( + await client.functionLoad(code, { + route: route, + }), + ).toEqual(name1); + + const flist = await client.functionList({ + withCode: true, + route: route, + }); + response = await client.functionDump(route); + const dump = ( + singleNodeRoute + ? response + : Object.values(response)[0] + ) as Buffer; + + // restore without cleaning the lib and/or overwrite option causes an error + await expect( + client.functionRestore(dump, { route: route }), + ).rejects.toThrow( + `Library ${name1} already exists`, + ); + + // APPEND policy also fails for the same reason (name collision) + await expect( + client.functionRestore(dump, { + policy: FunctionRestorePolicy.APPEND, + route: route, + }), + ).rejects.toThrow( + `Library ${name1} already exists`, + ); + + // REPLACE policy succeeds + expect( + await client.functionRestore(dump, { + policy: FunctionRestorePolicy.REPLACE, + route: route, + }), + ).toEqual("OK"); + // but nothing changed - all code overwritten + expect( + await client.functionList({ + withCode: true, + route: route, + }), + ).toEqual(flist); + + // create lib with another name, but with the same function names + expect( + await client.functionFlush({ + mode: FlushMode.SYNC, + route: route, + }), + ).toEqual("OK"); + code = generateLuaLibCode( + name2, + new Map([ + [name1, "return args[1]"], + [name2, "return #args"], + ]), + false, + ); + expect( + await client.functionLoad(code, { + route: route, + }), + ).toEqual(name2); + + // REPLACE policy now fails due to a name collision + await expect( + client.functionRestore(dump, { route: route }), + ).rejects.toThrow( + new RegExp( + `Function ${name1}|${name2} already exists`, + ), + ); + + // FLUSH policy succeeds, but deletes the second lib + expect( + await client.functionRestore(dump, { + policy: FunctionRestorePolicy.FLUSH, + route: route, + }), + ).toEqual("OK"); + expect( + await client.functionList({ + withCode: true, + route: route, + }), + ).toEqual(flist); + + // call restored functions + let res = await client.fcallWithRoute( + name1, + ["meow", "woem"], + { route: route }, + ); + + if (singleNodeRoute) { + expect(res).toEqual("meow"); + } else { + Object.values( + res as Record, + ).forEach((r) => expect(r).toEqual("meow")); + } + + res = await client.fcallWithRoute( + name2, + ["meow", "woem"], + { route: route }, + ); + + if (singleNodeRoute) { + expect(res).toEqual(2); + } else { + Object.values( + res as Record, + ).forEach((r) => expect(r).toEqual(2)); + } + } finally { + expect(await client.functionFlush()).toEqual("OK"); + client.close(); + } + }); + }, + ); + it( + "function kill key based write function", + async () => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + + const config = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + { requestTimeout: 10000 }, + ); + const client = + await GlideClusterClient.createClient(config); + const testClient = + await GlideClusterClient.createClient(config); + + try { + const libName = + "function_kill_key_based_write_function"; + const funcName = + "deadlock_write_function_with_key_based_route"; + const key = libName; + const code = createLuaLibWithLongRunningFunction( + libName, + funcName, + 6, + false, + ); + + const route: Routes = { + type: "primarySlotKey", + key: key, + }; + expect(await client.functionFlush()).toEqual("OK"); + + // nothing to kill + await expect( + client.functionKill(route), + ).rejects.toThrow(/notbusy/i); + + // load the lib + expect( + await client.functionLoad(code, { + replace: true, + route: route, + }), + ).toEqual(libName); + + let promise = null; + + try { + // call the function without await + promise = testClient.fcall(funcName, [key], []); + + let foundUnkillable = false; + let timeout = 4000; + await new Promise((resolve) => + setTimeout(resolve, 1000), + ); + + while (timeout >= 0) { + try { + // valkey kills a function with 5 sec delay + // but this will always throw an error in the test + await client.functionKill(route); + } catch (err) { + // looking for an error with "unkillable" in the message + // at that point we can break the loop + if ( + (err as Error).message + .toLowerCase() + .includes("unkillable") + ) { + foundUnkillable = true; + break; + } + } + + await new Promise((resolve) => + setTimeout(resolve, 500), + ); + timeout -= 500; + } + + expect(foundUnkillable).toBeTruthy(); + } finally { + // If function wasn't killed, and it didn't time out - it blocks the server and cause rest + // test to fail. Wait for the function to complete (we cannot kill it) + expect(await promise).toContain("Timed out"); + } + } finally { + expect(await client.functionFlush()).toEqual("OK"); + client.close(); + testClient.close(); + } + }, + TIMEOUT, + ); + it("function dump function restore in transaction", async () => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + + const config = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + ); + const client = await GlideClusterClient.createClient(config); + const route: SlotKeyTypes = { + key: uuidv4(), + type: "primarySlotKey", + }; + expect(await client.functionFlush()).toEqual("OK"); + + try { + const name1 = "Foster"; + const name2 = "Dogster"; + // function returns first argument + const code = generateLuaLibCode( + name1, + new Map([[name2, "return args[1]"]]), + false, + ); + expect( + await client.functionLoad(code, { + replace: true, + route: route, + }), + ).toEqual(name1); + + // Verify functionDump + let transaction = new ClusterTransaction().functionDump(); + const result = await client.exec(transaction, { + decoder: Decoder.Bytes, + route: route, + }); + const data = result?.[0] as Buffer; + + // Verify functionRestore + transaction = new ClusterTransaction() + .functionRestore(data, FunctionRestorePolicy.REPLACE) + .fcall(name2, [], ["meow"]); + expect( + await client.exec(transaction, { route: route }), + ).toEqual(["OK", "meow"]); + } finally { + expect(await client.functionFlush()).toEqual("OK"); + client.close(); + } + }); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `randomKey test_%p`, + async (protocol) => { + client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key = uuidv4(); + + // setup: delete all keys + expect(await client.flushall({ mode: FlushMode.SYNC })).toEqual( + "OK", + ); + + // no keys exist so randomKey returns null + expect(await client.randomKey()).toBeNull(); + + expect(await client.set(key, "foo")).toEqual("OK"); + // `key` should be the only existing key, so randomKey should return `key` + expect(await client.randomKey({ decoder: Decoder.Bytes })).toEqual( + Buffer.from(key), + ); + expect(await client.randomKey({ route: "allPrimaries" })).toEqual( + key, + ); + + client.close(); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "watch test_%p", + async (protocol) => { + const client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const key3 = "{key}-3" + uuidv4(); + const key4 = "{key}-4" + uuidv4(); + const setFoobarTransaction = new ClusterTransaction(); + const setHelloTransaction = new ClusterTransaction(); + + // Returns null when a watched key is modified before it is executed in a transaction command. + // Transaction commands are not performed. + expect(await client.watch([key1, key2, key3])).toEqual("OK"); + expect(await client.set(key2, "hello")).toEqual("OK"); + setFoobarTransaction + .set(key1, "foobar") + .set(key2, "foobar") + .set(key3, "foobar"); + let results = await client.exec(setFoobarTransaction); + expect(results).toEqual(null); + // sanity check + expect(await client.get(key1)).toEqual(null); + expect(await client.get(key2)).toEqual("hello"); + expect(await client.get(key3)).toEqual(null); + + // Transaction executes command successfully with a read command on the watch key before + // transaction is executed. + expect(await client.watch([key1, key2, Buffer.from(key3)])).toEqual( + "OK", + ); + expect(await client.get(key2)).toEqual("hello"); + results = await client.exec(setFoobarTransaction); + expect(results).toEqual(["OK", "OK", "OK"]); + // sanity check + expect(await client.get(key1)).toEqual("foobar"); + expect(await client.get(key2)).toEqual("foobar"); + expect(await client.get(key3)).toEqual("foobar"); + + // Transaction executes command successfully with unmodified watched keys + expect(await client.watch([key1, key2, key3])).toEqual("OK"); + results = await client.exec(setFoobarTransaction); + expect(results).toEqual(["OK", "OK", "OK"]); + // sanity check + expect(await client.get(key1)).toEqual("foobar"); + expect(await client.get(key2)).toEqual("foobar"); + expect(await client.get(key3)).toEqual("foobar"); + + // Transaction executes command successfully with a modified watched key but is not in the + // transaction. + expect(await client.watch([key4])).toEqual("OK"); + setHelloTransaction + .set(key1, "hello") + .set(key2, "hello") + .set(key3, "hello"); + results = await client.exec(setHelloTransaction); + expect(results).toEqual(["OK", "OK", "OK"]); + // sanity check + expect(await client.get(key1)).toEqual("hello"); + expect(await client.get(key2)).toEqual("hello"); + expect(await client.get(key3)).toEqual("hello"); + + // WATCH can not have an empty String array parameter + await expect(client.watch([])).rejects.toThrow(RequestError); + + client.close(); + }, + TIMEOUT, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "unwatch test_%p", + async (protocol) => { + const client = await GlideClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const setFoobarTransaction = new ClusterTransaction(); + + // UNWATCH returns OK when there no watched keys + expect(await client.unwatch()).toEqual("OK"); + + // Transaction executes successfully after modifying a watched key then calling UNWATCH + expect(await client.watch([key1, key2])).toEqual("OK"); + expect(await client.set(key2, "hello")).toEqual("OK"); + expect(await client.unwatch()).toEqual("OK"); + expect(await client.unwatch({ route: "allPrimaries" })).toEqual( + "OK", + ); + setFoobarTransaction.set(key1, "foobar").set(key2, "foobar"); + const results = await client.exec(setFoobarTransaction); + expect(results).toEqual(["OK", "OK"]); + // sanity check + expect(await client.get(key1)).toEqual("foobar"); + expect(await client.get(key2)).toEqual("foobar"); + + client.close(); + }, + TIMEOUT, + ); +}); diff --git a/node/tests/PubSub.test.ts b/node/tests/PubSub.test.ts new file mode 100644 index 0000000000..c065fa89ca --- /dev/null +++ b/node/tests/PubSub.test.ts @@ -0,0 +1,4028 @@ +/** + * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + */ + +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + it, +} from "@jest/globals"; +import { v4 as uuidv4 } from "uuid"; +import { + BaseClientConfiguration, + ConfigurationError, + GlideClient, + GlideClientConfiguration, + GlideClusterClient, + GlideClusterClientConfiguration, + ProtocolVersion, + PubSubMsg, + TimeoutError, +} from ".."; +import RedisCluster from "../../utils/TestUtils"; +import { + flushAndCloseClient, + parseCommandLineArgs, + parseEndpoints, +} from "./TestUtilities"; + +export type TGlideClient = GlideClient | GlideClusterClient; + +/** + * Enumeration for specifying the method of PUBSUB subscription. + */ +const MethodTesting = { + Async: 0, // Uses asynchronous subscription method. + Sync: 1, // Uses synchronous subscription method. + Callback: 2, // Uses callback-based subscription method. +}; + +const TIMEOUT = 120000; +describe("PubSub", () => { + let cmeCluster: RedisCluster; + let cmdCluster: RedisCluster; + beforeAll(async () => { + const standaloneAddresses = + parseCommandLineArgs()["standalone-endpoints"]; + const clusterAddresses = parseCommandLineArgs()["cluster-endpoints"]; + // Connect to cluster or create a new one based on the parsed addresses + cmdCluster = standaloneAddresses + ? await RedisCluster.initFromExistingCluster( + parseEndpoints(standaloneAddresses), + ) + : await RedisCluster.createCluster(false, 1, 1); + cmeCluster = clusterAddresses + ? await RedisCluster.initFromExistingCluster( + parseEndpoints(clusterAddresses), + ) + : await RedisCluster.createCluster(true, 3, 1); + }, 40000); + afterEach(async () => { + await flushAndCloseClient(false, cmdCluster.getAddresses()); + await flushAndCloseClient(true, cmeCluster.getAddresses()); + }); + afterAll(async () => { + await cmeCluster.close(); + await cmdCluster.close(); + }); + + async function createClients( + clusterMode: boolean, + options: GlideClusterClientConfiguration | GlideClientConfiguration, + options2: GlideClusterClientConfiguration | GlideClientConfiguration, + pubsubSubscriptions: + | GlideClientConfiguration.PubSubSubscriptions + | GlideClusterClientConfiguration.PubSubSubscriptions, + pubsubSubscriptions2?: + | GlideClientConfiguration.PubSubSubscriptions + | GlideClusterClientConfiguration.PubSubSubscriptions, + ): Promise<[TGlideClient, TGlideClient]> { + let client: TGlideClient | undefined; + + if (clusterMode) { + try { + options.pubsubSubscriptions = pubsubSubscriptions; + client = await GlideClusterClient.createClient(options); + options2.pubsubSubscriptions = pubsubSubscriptions2; + const client2 = await GlideClusterClient.createClient(options2); + return [client, client2]; + } catch (error) { + if (client) { + client.close(); + } + + throw error; + } + } else { + try { + options.pubsubSubscriptions = pubsubSubscriptions; + client = await GlideClient.createClient(options); + options2.pubsubSubscriptions = pubsubSubscriptions2; + const client2 = await GlideClient.createClient(options2); + return [client, client2]; + } catch (error) { + if (client) { + client.close(); + } + + throw error; + } + } + } + + const getOptions = ( + clusterMode: boolean, + protocol: ProtocolVersion = ProtocolVersion.RESP3, + ): BaseClientConfiguration => { + if (clusterMode) { + return { + addresses: cmeCluster.ports().map((port) => ({ + host: "localhost", + port, + })), + protocol, + }; + } + + return { + addresses: cmdCluster.ports().map((port) => ({ + host: "localhost", + port, + })), + protocol, + }; + }; + + function decodePubSubMsg(msg: PubSubMsg | null = null) { + if (!msg) { + return { + message: "", + channel: "", + pattern: null, + }; + } + + const stringMsg = Buffer.from(msg.message).toString("utf-8"); + const stringChannel = Buffer.from(msg.channel).toString("utf-8"); + const stringPattern = msg.pattern + ? Buffer.from(msg.pattern).toString("utf-8") + : null; + + return { + message: stringMsg, + channel: stringChannel, + pattern: stringPattern, + }; + } + + async function getMessageByMethod( + method: number, + client: TGlideClient, + messages: PubSubMsg[] | null = null, + index?: number, + ) { + if (method === MethodTesting.Async) { + const pubsubMessage = await client.getPubSubMessage(); + return decodePubSubMsg(pubsubMessage); + } else if (method === MethodTesting.Sync) { + const pubsubMessage = client.tryGetPubSubMessage(); + return decodePubSubMsg(pubsubMessage); + } else { + if (messages && index !== null) { + return decodePubSubMsg(messages[index!]); + } + + throw new Error( + "Messages and index must be provided for this method.", + ); + } + } + + async function checkNoMessagesLeft( + method: number, + client: TGlideClient, + callback: PubSubMsg[] | null = [], + expectedCallbackMessagesCount = 0, + ) { + if (method === MethodTesting.Async) { + try { + // Assert there are no messages to read + await Promise.race([ + client.getPubSubMessage(), + new Promise((_, reject) => + setTimeout( + () => reject(new TimeoutError("TimeoutError")), + 3000, + ), + ), + ]); + throw new Error("Expected TimeoutError but got a message."); + } catch (error) { + if (!(error instanceof TimeoutError)) { + throw error; + } + } + } else if (method === MethodTesting.Sync) { + const message = client.tryGetPubSubMessage(); + expect(message).toBe(null); + } else { + if (callback === null) { + throw new Error("Callback must be provided."); + } + + expect(callback.length).toBe(expectedCallbackMessagesCount); + } + } + + function createPubSubSubscription( + clusterMode: boolean, + clusterChannelsAndPatterns: Partial< + Record< + GlideClusterClientConfiguration.PubSubChannelModes, + Set + > + >, + standaloneChannelsAndPatterns: Partial< + Record> + >, + callback?: (msg: PubSubMsg, context: PubSubMsg[]) => void, + context: PubSubMsg[] | null = null, + ) { + if (clusterMode) { + const mySubscriptions: GlideClusterClientConfiguration.PubSubSubscriptions = + { + channelsAndPatterns: clusterChannelsAndPatterns, + callback: callback, + context: context, + }; + return mySubscriptions; + } + + const mySubscriptions: GlideClientConfiguration.PubSubSubscriptions = { + channelsAndPatterns: standaloneChannelsAndPatterns, + callback: callback, + context: context, + }; + return mySubscriptions; + } + + async function clientCleanup( + client: TGlideClient, + clusterModeSubs?: GlideClusterClientConfiguration.PubSubSubscriptions, + ) { + if (client === null) { + return; + } + + if (clusterModeSubs) { + for (const [channelType, channelPatterns] of Object.entries( + clusterModeSubs.channelsAndPatterns, + )) { + let cmd; + + if ( + channelType === + GlideClusterClientConfiguration.PubSubChannelModes.Exact.toString() + ) { + cmd = "UNSUBSCRIBE"; + } else if ( + channelType === + GlideClusterClientConfiguration.PubSubChannelModes.Pattern.toString() + ) { + cmd = "PUNSUBSCRIBE"; + } else if (!cmeCluster.checkIfServerVersionLessThan("7.0.0")) { + cmd = "SUNSUBSCRIBE"; + } else { + // Disregard sharded config for versions < 7.0.0 + continue; + } + + for (const channelPattern of channelPatterns) { + await client.customCommand([cmd, channelPattern]); + } + } + } + + client.close(); + // Wait briefly to ensure closure is completed + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + function newMessage(msg: PubSubMsg, context: PubSubMsg[]): void { + context.push(msg); + } + + const testCases: [ + boolean, + (typeof MethodTesting)[keyof typeof MethodTesting], + ][] = [ + [true, MethodTesting.Async], + [true, MethodTesting.Sync], + [true, MethodTesting.Callback], + [false, MethodTesting.Async], + [false, MethodTesting.Sync], + [false, MethodTesting.Callback], + ]; + + /** + * Tests the basic happy path for exact PUBSUB functionality. + * + * This test covers the basic PUBSUB flow using three different methods: + * Async, Sync, and Callback. It verifies that a message published to a + * specific channel is correctly received by a subscriber. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + * @param method - Specifies the method of PUBSUB subscription (Async, Sync, Callback). + */ + it.each(testCases)( + `pubsub exact happy path test_%p%p`, + async (clusterMode, method) => { + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let listeningClient: TGlideClient; + let publishingClient: TGlideClient; + + try { + const channel = uuidv4(); + const message = uuidv4(); + const options = getOptions(clusterMode); + let context: PubSubMsg[] | null = null; + let callback; + + if (method === MethodTesting.Callback) { + context = []; + callback = newMessage; + } + + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([channel]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set([channel]), + }, + callback, + context, + ); + [listeningClient, publishingClient] = await createClients( + clusterMode, + options, + getOptions(clusterMode), + pubSub, + ); + + const result = await publishingClient.publish(message, channel); + + if (clusterMode) { + expect(result).toEqual(1); + } + + // Allow the message to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const pubsubMessage = await getMessageByMethod( + method, + listeningClient, + context, + 0, + ); + expect(pubsubMessage.message).toEqual(message); + expect(pubsubMessage.channel).toEqual(channel); + expect(pubsubMessage.pattern).toEqual(null); + + await checkNoMessagesLeft(method, listeningClient, context, 1); + } finally { + await clientCleanup(publishingClient!); + await clientCleanup( + listeningClient!, + clusterMode ? pubSub! : undefined, + ); + } + }, + TIMEOUT, + ); + + /** + * Test the coexistence of async and sync message retrieval methods in exact PUBSUB. + * + * This test covers the scenario where messages are published to a channel + * and received using both async and sync methods to ensure that both methods + * can coexist and function correctly. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true, false])( + "pubsub exact happy path coexistence test_%p", + async (clusterMode) => { + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let listeningClient: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + + try { + const channel = uuidv4(); + const message = uuidv4(); + const message2 = uuidv4(); + + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([channel]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set([channel]), + }, + ); + + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + for (const msg of [message, message2]) { + const result = await publishingClient.publish(msg, channel); + + if (clusterMode) { + expect(result).toEqual(1); + } + } + + // Allow the message to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const asyncMsgRes = await listeningClient.getPubSubMessage(); + const syncMsgRes = listeningClient.tryGetPubSubMessage(); + expect(syncMsgRes).toBeTruthy(); + + const asyncMsg = decodePubSubMsg(asyncMsgRes); + const syncMsg = decodePubSubMsg(syncMsgRes); + + expect([message, message2]).toContain(asyncMsg.message); + expect(asyncMsg.channel).toEqual(channel); + expect(asyncMsg.pattern).toBeNull(); + + expect([message, message2]).toContain(syncMsg.message); + expect(syncMsg.channel).toEqual(channel); + expect(syncMsg.pattern).toBeNull(); + + expect(asyncMsg.message).not.toEqual(syncMsg.message); + + // Assert there are no messages to read + await checkNoMessagesLeft(MethodTesting.Async, listeningClient); + expect(listeningClient.tryGetPubSubMessage()).toBeNull(); + } finally { + await clientCleanup(publishingClient!); + await clientCleanup( + listeningClient!, + clusterMode ? pubSub! : undefined, + ); + } + }, + TIMEOUT, + ); + + /** + * Tests publishing and receiving messages across many channels in exact PUBSUB. + * + * This test covers the scenario where multiple channels each receive their own + * unique message. It verifies that messages are correctly published and received + * using different retrieval methods: async, sync, and callback. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + * @param method - Specifies the method of PUBSUB subscription (Async, Sync, Callback). + */ + it.each(testCases)( + "pubsub exact happy path many channels test_%p_%p", + async (clusterMode, method) => { + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let listeningClient: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + const NUM_CHANNELS = 256; + const shardPrefix = "{same-shard}"; + + try { + // Create a map of channels to random messages with shard prefix + const channelsAndMessages: Record = {}; + + for (let i = 0; i < NUM_CHANNELS; i++) { + const channel = `${shardPrefix}${uuidv4()}`; + const message = uuidv4(); + channelsAndMessages[channel] = message; + } + + let context: PubSubMsg[] | null = null; + let callback; + + if (method === MethodTesting.Callback) { + context = []; + callback = newMessage; + } + + // Create PUBSUB subscription for the test + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set(Object.keys(channelsAndMessages)), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set(Object.keys(channelsAndMessages)), + }, + callback, + context, + ); + + // Create clients for listening and publishing + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + // Publish messages to each channel + for (const [channel, message] of Object.entries( + channelsAndMessages, + )) { + const result = await publishingClient.publish( + message, + channel, + ); + + if (clusterMode) { + expect(result).toEqual(1); + } + } + + // Allow the messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Check if all messages are received correctly + for (let index = 0; index < NUM_CHANNELS; index++) { + const pubsubMsg = await getMessageByMethod( + method, + listeningClient, + context, + index, + ); + expect( + pubsubMsg.channel in channelsAndMessages, + ).toBeTruthy(); + expect(pubsubMsg.message).toEqual( + channelsAndMessages[pubsubMsg.channel], + ); + expect(pubsubMsg.pattern).toBeNull(); + delete channelsAndMessages[pubsubMsg.channel]; + } + + // Check that we received all messages + expect(Object.keys(channelsAndMessages).length).toEqual(0); + + // Check no messages left + await checkNoMessagesLeft( + method, + listeningClient, + context, + NUM_CHANNELS, + ); + } finally { + // Cleanup clients + if (listeningClient) { + await clientCleanup( + listeningClient, + clusterMode ? pubSub! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + + /** + * Tests publishing and receiving messages across many channels in exact PUBSUB, + * ensuring coexistence of async and sync retrieval methods. + * + * This test covers scenarios where multiple channels each receive their own unique message. + * It verifies that messages are correctly published and received using both async and sync methods + * to ensure that both methods can coexist and function correctly. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true, false])( + "pubsub exact happy path many channels coexistence test_%p", + async (clusterMode) => { + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let listeningClient: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + const NUM_CHANNELS = 256; + const shardPrefix = "{same-shard}"; + + try { + // Create a map of channels to random messages with shard prefix + const channelsAndMessages: Record = {}; + + for (let i = 0; i < NUM_CHANNELS; i++) { + const channel = `${shardPrefix}${uuidv4()}`; + const message = uuidv4(); + channelsAndMessages[channel] = message; + } + + // Create PUBSUB subscription for the test + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set(Object.keys(channelsAndMessages)), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set(Object.keys(channelsAndMessages)), + }, + ); + + // Create clients for listening and publishing + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + // Publish messages to each channel + for (const [channel, message] of Object.entries( + channelsAndMessages, + )) { + const result = await publishingClient.publish( + message, + channel, + ); + + if (clusterMode) { + expect(result).toEqual(1); + } + } + + // Allow the messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Check if all messages are received correctly by each method + for (let index = 0; index < NUM_CHANNELS; index++) { + const method = + index % 2 === 0 + ? MethodTesting.Sync + : MethodTesting.Async; + const pubsubMsg = await getMessageByMethod( + method, + listeningClient, + ); + + expect( + pubsubMsg.channel in channelsAndMessages, + ).toBeTruthy(); + expect(pubsubMsg.message).toEqual( + channelsAndMessages[pubsubMsg.channel], + ); + expect(pubsubMsg.pattern).toBeNull(); + delete channelsAndMessages[pubsubMsg.channel]; + } + + // Check that we received all messages + expect(Object.keys(channelsAndMessages).length).toEqual(0); + + // Assert there are no messages to read + await checkNoMessagesLeft(MethodTesting.Async, listeningClient); + expect(listeningClient.tryGetPubSubMessage()).toBeNull(); + } finally { + // Cleanup clients + if (listeningClient) { + await clientCleanup( + listeningClient, + clusterMode ? pubSub! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + + /** + * Test sharded PUBSUB functionality with different message retrieval methods. + * + * This test covers the sharded PUBSUB flow using three different methods: + * Async, Sync, and Callback. It verifies that a message published to a + * specific sharded channel is correctly received by a subscriber. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + * @param method - Specifies the method of PUBSUB subscription (Async, Sync, Callback). + */ + it.each([ + [true, MethodTesting.Async], + [true, MethodTesting.Sync], + [true, MethodTesting.Callback], + ])( + "sharded pubsub test_%p_%p", + async (clusterMode, method) => { + const minVersion = "7.0.0"; + + if (cmeCluster.checkIfServerVersionLessThan(minVersion)) return; + + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let listeningClient: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + const channel = uuidv4(); + const message = uuidv4(); + const publishResponse = 1; + + try { + let context: PubSubMsg[] | null = null; + let callback; + + if (method === MethodTesting.Callback) { + context = []; + callback = newMessage; + } + + // Create PUBSUB subscription for the test + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set([channel]), + }, + {}, + callback, + context, + ); + + // Create clients for listening and publishing + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + const result = await ( + publishingClient as GlideClusterClient + ).publish(message, channel, true); + + expect(result).toEqual(publishResponse); + + // Allow the message to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const pubsubMsg = await getMessageByMethod( + method, + listeningClient, + context, + 0, + ); + + expect(pubsubMsg.message).toEqual(message); + expect(pubsubMsg.channel).toEqual(channel); + expect(pubsubMsg.pattern).toBeNull(); + + // Assert there are no messages to read + await checkNoMessagesLeft(method, listeningClient, context, 1); + } finally { + // Cleanup clients + if (listeningClient) { + await clientCleanup( + listeningClient, + clusterMode ? pubSub! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + + /** + * Test sharded PUBSUB with co-existence of multiple messages. + * + * This test verifies the behavior of sharded PUBSUB when multiple messages are published + * to the same sharded channel. It ensures that both async and sync methods of message retrieval + * function correctly in this scenario. + * + * It covers the scenario where messages are published to a sharded channel and received using + * both async and sync methods. This ensures that the asynchronous and synchronous message + * retrieval methods can coexist without interfering with each other and operate as expected. + */ + it( + "sharded pubsub co-existence test", + async () => { + const minVersion = "7.0.0"; + + if (cmeCluster.checkIfServerVersionLessThan(minVersion)) return; + + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let listeningClient: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + const channel = uuidv4(); + const message = uuidv4(); + const message2 = uuidv4(); + + try { + // Create PUBSUB subscription for the test + pubSub = createPubSubSubscription( + true, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set([channel]), + }, + {}, + ); + + // Create clients for listening and publishing + [listeningClient, publishingClient] = await createClients( + true, + getOptions(true), + getOptions(true), + pubSub, + ); + + let result = await ( + publishingClient as GlideClusterClient + ).publish(message, channel, true); + expect(result).toEqual(1); + + result = await (publishingClient as GlideClusterClient).publish( + message2, + channel, + true, + ); + expect(result).toEqual(1); + + // Allow the messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const asyncMsgRes = await listeningClient.getPubSubMessage(); + const syncMsgRes = listeningClient.tryGetPubSubMessage(); + expect(syncMsgRes).toBeTruthy(); + + const asyncMsg = decodePubSubMsg(asyncMsgRes); + const syncMsg = decodePubSubMsg(syncMsgRes); + + expect([message, message2]).toContain(asyncMsg.message); + expect(asyncMsg.channel).toEqual(channel); + expect(asyncMsg.pattern).toBeNull(); + + expect([message, message2]).toContain(syncMsg.message); + expect(syncMsg.channel).toEqual(channel); + expect(syncMsg.pattern).toBeNull(); + + expect(asyncMsg.message).not.toEqual(syncMsg.message); + + // Assert there are no messages to read + await checkNoMessagesLeft(MethodTesting.Async, listeningClient); + expect(listeningClient.tryGetPubSubMessage()).toBeNull(); + } finally { + // Cleanup clients + if (listeningClient) { + await clientCleanup(listeningClient, pubSub!); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + + /** + * Test sharded PUBSUB with multiple channels and different message retrieval methods. + * + * This test verifies the behavior of sharded PUBSUB when multiple messages are published + * across multiple sharded channels. It covers three different message retrieval methods: + * Async, Sync, and Callback. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + * @param method - Specifies the method of PUBSUB subscription (Async, Sync, Callback). + */ + it.each([ + [true, MethodTesting.Async], + [true, MethodTesting.Sync], + [true, MethodTesting.Callback], + ])( + "sharded pubsub many channels test_%p_%p", + async (clusterMode, method) => { + const minVersion = "7.0.0"; + + if (cmeCluster.checkIfServerVersionLessThan(minVersion)) return; + + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let listeningClient: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + const NUM_CHANNELS = 256; + const shardPrefix = "{same-shard}"; + const publishResponse = 1; + + // Create a map of channels to random messages with shard prefix + const channelsAndMessages: Record = {}; + + for (let i = 0; i < NUM_CHANNELS; i++) { + const channel = `${shardPrefix}${uuidv4()}`; + const message = uuidv4(); + channelsAndMessages[channel] = message; + } + + try { + let context: PubSubMsg[] | null = null; + let callback; + + if (method === MethodTesting.Callback) { + context = []; + callback = newMessage; + } + + // Create PUBSUB subscription for the test + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set( + Object.keys(channelsAndMessages), + ), + }, + {}, + callback, + context, + ); + + // Create clients for listening and publishing + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + // Publish messages to each channel + for (const [channel, message] of Object.entries( + channelsAndMessages, + )) { + const result = await ( + publishingClient as GlideClusterClient + ).publish(message, channel, true); + expect(result).toEqual(publishResponse); + } + + // Allow the messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Check if all messages are received correctly + for (let index = 0; index < NUM_CHANNELS; index++) { + const pubsubMsg = await getMessageByMethod( + method, + listeningClient, + context, + index, + ); + + expect( + pubsubMsg.channel in channelsAndMessages, + ).toBeTruthy(); + expect(pubsubMsg.message).toEqual( + channelsAndMessages[pubsubMsg.channel], + ); + expect(pubsubMsg.pattern).toBeNull(); + delete channelsAndMessages[pubsubMsg.channel]; + } + + // Check that we received all messages + expect(Object.keys(channelsAndMessages).length).toEqual(0); + + // Assert there are no more messages to read + await checkNoMessagesLeft( + method, + listeningClient, + context, + NUM_CHANNELS, + ); + } finally { + // Cleanup clients + if (listeningClient) { + await clientCleanup( + listeningClient, + clusterMode ? pubSub! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + + /** + * Test PUBSUB with pattern subscription using different message retrieval methods. + * + * This test verifies the behavior of PUBSUB when subscribing to a pattern and receiving + * messages using three different methods: Async, Sync, and Callback. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + * @param method - Specifies the method of PUBSUB subscription (Async, Sync, Callback). + */ + it.each(testCases)( + "pubsub pattern test_%p_%p", + async (clusterMode, method) => { + const PATTERN = `{{channel}}:*`; + const channels: Record = { + [`{{channel}}:${uuidv4()}`]: uuidv4(), + [`{{channel}}:${uuidv4()}`]: uuidv4(), + }; + + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let listeningClient: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + + let context: PubSubMsg[] | null = null; + let callback; + + if (method === MethodTesting.Callback) { + context = []; + callback = newMessage; + } + + try { + // Create PUBSUB subscription for the test + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Pattern]: new Set([PATTERN]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Pattern]: + new Set([PATTERN]), + }, + callback, + context, + ); + + // Create clients for listening and publishing + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + // Publish messages to each channel + for (const [channel, message] of Object.entries(channels)) { + const result = await publishingClient.publish( + message, + channel, + ); + + if (clusterMode) { + expect(result).toEqual(1); + } + } + + // Allow the messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Check if all messages are received correctly + for (let index = 0; index < 2; index++) { + const pubsubMsg = await getMessageByMethod( + method, + listeningClient, + context, + index, + ); + expect(pubsubMsg.channel in channels).toBeTruthy(); + expect(pubsubMsg.message).toEqual( + channels[pubsubMsg.channel], + ); + expect(pubsubMsg.pattern).toEqual(PATTERN); + delete channels[pubsubMsg.channel]; + } + + // Check that we received all messages + expect(Object.keys(channels).length).toEqual(0); + + // Assert there are no more messages to read + await checkNoMessagesLeft(method, listeningClient, context, 2); + } finally { + // Cleanup clients + if (listeningClient) { + await clientCleanup( + listeningClient, + clusterMode ? pubSub! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + + /** + * Tests the coexistence of async and sync message retrieval methods in pattern-based PUBSUB. + * + * This test covers the scenario where messages are published to a channel that match a specified pattern + * and received using both async and sync methods to ensure that both methods + * can coexist and function correctly. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true, false])( + "pubsub pattern coexistence test_%p", + async (clusterMode) => { + const PATTERN = `{{channel}}:*`; + const channels: Record = { + [`{{channel}}:${uuidv4()}`]: uuidv4(), + [`{{channel}}:${uuidv4()}`]: uuidv4(), + }; + + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let listeningClient: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + + try { + // Create PUBSUB subscription for the test + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Pattern]: new Set([PATTERN]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Pattern]: + new Set([PATTERN]), + }, + ); + + // Create clients for listening and publishing + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + // Publish messages to each channel + for (const [channel, message] of Object.entries(channels)) { + const result = await publishingClient.publish( + message, + channel, + ); + + if (clusterMode) { + expect(result).toEqual(1); + } + } + + // Allow the messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Check if all messages are received correctly by each method + for (let index = 0; index < 2; index++) { + const method = + index % 2 === 0 + ? MethodTesting.Async + : MethodTesting.Sync; + const pubsubMsg = await getMessageByMethod( + method, + listeningClient, + ); + + expect(Object.keys(channels)).toContain(pubsubMsg.channel); + expect(pubsubMsg.message).toEqual( + channels[pubsubMsg.channel], + ); + expect(pubsubMsg.pattern).toEqual(PATTERN); + delete channels[pubsubMsg.channel]; + } + + // Check that we received all messages + expect(Object.keys(channels).length).toEqual(0); + + // Assert there are no more messages to read + await checkNoMessagesLeft(MethodTesting.Async, listeningClient); + expect(listeningClient.tryGetPubSubMessage()).toBeNull(); + } finally { + // Cleanup clients + if (listeningClient) { + await clientCleanup( + listeningClient, + clusterMode ? pubSub! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + + /** + * Tests publishing and receiving messages across many channels in pattern-based PUBSUB. + * + * This test covers the scenario where messages are published to multiple channels that match a specified pattern + * and received. It verifies that messages are correctly published and received + * using different retrieval methods: async, sync, and callback. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + * @param method - Specifies the method of PUBSUB subscription (Async, Sync, Callback). + */ + it.each(testCases)( + "pubsub pattern many channels test_%p", + async (clusterMode, method) => { + const NUM_CHANNELS = 256; + const PATTERN = "{{channel}}:*"; + const channels: Record = {}; + + for (let i = 0; i < NUM_CHANNELS; i++) { + const channel = `{{channel}}:${uuidv4()}`; + const message = uuidv4(); + channels[channel] = message; + } + + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let listeningClient: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + let context: PubSubMsg[] | null = null; + let callback; + + if (method === MethodTesting.Callback) { + context = []; + callback = newMessage; + } + + try { + // Create PUBSUB subscription for the test + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Pattern]: new Set([PATTERN]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Pattern]: + new Set([PATTERN]), + }, + callback, + context, + ); + + // Create clients for listening and publishing + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + // Publish messages to each channel + for (const [channel, message] of Object.entries(channels)) { + const result = await publishingClient.publish( + message, + channel, + ); + + if (clusterMode) { + expect(result).toEqual(1); + } + } + + // Allow the messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Check if all messages are received correctly + for (let index = 0; index < NUM_CHANNELS; index++) { + const pubsubMsg = await getMessageByMethod( + method, + listeningClient, + context, + index, + ); + expect(pubsubMsg.channel in channels).toBeTruthy(); + expect(pubsubMsg.message).toEqual( + channels[pubsubMsg.channel], + ); + expect(pubsubMsg.pattern).toEqual(PATTERN); + delete channels[pubsubMsg.channel]; + } + + // Check that we received all messages + expect(Object.keys(channels).length).toEqual(0); + + // Assert there are no more messages to read + await checkNoMessagesLeft( + method, + listeningClient, + context, + NUM_CHANNELS, + ); + } finally { + // Cleanup clients + if (listeningClient) { + await clientCleanup( + listeningClient, + clusterMode ? pubSub! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + + /** + * Tests combined exact and pattern PUBSUB with one client. + * + * This test verifies that a single client can correctly handle both exact and pattern PUBSUB + * subscriptions. It covers the following scenarios: + * - Subscribing to multiple channels with exact names and verifying message reception. + * - Subscribing to channels using a pattern and verifying message reception. + * - Ensuring that messages are correctly published and received using different retrieval methods (async, sync, callback). + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + * @param method - Specifies the method of PUBSUB subscription (Async, Sync, Callback). + */ + it.each(testCases)( + "pubsub combined exact and pattern test_%p_%p", + async (clusterMode, method) => { + const NUM_CHANNELS = 256; + const PATTERN = "{{pattern}}:*"; + + // Create dictionaries of channels and their corresponding messages + const exactChannelsAndMessages: Record = {}; + const patternChannelsAndMessages: Record = {}; + + for (let i = 0; i < NUM_CHANNELS; i++) { + const exactChannel = `{{channel}}:${uuidv4()}`; + const patternChannel = `{{pattern}}:${uuidv4()}`; + exactChannelsAndMessages[exactChannel] = uuidv4(); + patternChannelsAndMessages[patternChannel] = uuidv4(); + } + + const allChannelsAndMessages: Record = { + ...exactChannelsAndMessages, + ...patternChannelsAndMessages, + }; + + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let listeningClient: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + let context: PubSubMsg[] | null = null; + let callback; + + if (method === MethodTesting.Callback) { + context = []; + callback = newMessage; + } + + try { + // Setup PUBSUB for exact channels and pattern + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set( + Object.keys(exactChannelsAndMessages), + ), + [GlideClusterClientConfiguration.PubSubChannelModes + .Pattern]: new Set([PATTERN]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set(Object.keys(exactChannelsAndMessages)), + [GlideClientConfiguration.PubSubChannelModes.Pattern]: + new Set([PATTERN]), + }, + callback, + context, + ); + + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + // Publish messages to all channels + for (const [channel, message] of Object.entries( + allChannelsAndMessages, + )) { + const result = await publishingClient.publish( + message, + channel, + ); + + if (clusterMode) { + expect(result).toEqual(1); + } + } + + // Allow the messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const length = Object.keys(allChannelsAndMessages).length; + + // Check if all messages are received correctly + for (let index = 0; index < length; index++) { + const pubsubMsg: PubSubMsg = await getMessageByMethod( + method, + listeningClient, + context, + index, + ); + const pattern = + pubsubMsg.channel in patternChannelsAndMessages + ? PATTERN + : null; + expect( + pubsubMsg.channel in allChannelsAndMessages, + ).toBeTruthy(); + expect(pubsubMsg.message).toEqual( + allChannelsAndMessages[pubsubMsg.channel], + ); + expect(pubsubMsg.pattern).toEqual(pattern); + delete allChannelsAndMessages[pubsubMsg.channel]; + } + + // Check that we received all messages + expect(Object.keys(allChannelsAndMessages).length).toEqual(0); + + await checkNoMessagesLeft( + method, + listeningClient, + context, + NUM_CHANNELS * 2, + ); + } finally { + // Cleanup clients + if (listeningClient) { + await clientCleanup( + listeningClient, + clusterMode ? pubSub! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + + /** + * Tests combined exact and pattern PUBSUB with multiple clients, one for each subscription. + * + * This test verifies that separate clients can correctly handle both exact and pattern PUBSUB + * subscriptions. It covers the following scenarios: + * - Subscribing to multiple channels with exact names and verifying message reception. + * - Subscribing to channels using a pattern and verifying message reception. + * - Ensuring that messages are correctly published and received using different retrieval methods (async, sync, callback). + * - Verifying that no messages are left unread. + * - Properly unsubscribing from all channels to avoid interference with other tests. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + * @param method - Specifies the method of PUBSUB subscription (Async, Sync, Callback). + */ + it.each(testCases)( + "pubsub combined exact and pattern multiple clients test_%p_%p", + async (clusterMode, method) => { + const NUM_CHANNELS = 256; + const PATTERN = "{{pattern}}:*"; + + // Create dictionaries of channels and their corresponding messages + const exactChannelsAndMessages: Record = {}; + const patternChannelsAndMessages: Record = {}; + + for (let i = 0; i < NUM_CHANNELS; i++) { + const exactChannel = `{{channel}}:${uuidv4()}`; + const patternChannel = `{{pattern}}:${uuidv4()}`; + exactChannelsAndMessages[exactChannel] = uuidv4(); + patternChannelsAndMessages[patternChannel] = uuidv4(); + } + + const allChannelsAndMessages = { + ...exactChannelsAndMessages, + ...patternChannelsAndMessages, + }; + + let pubSubExact: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let pubSubPattern: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let listeningClientExact: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + let listeningClientPattern: TGlideClient | null = null; + let clientDontCare: TGlideClient | null = null; + let contextExact: PubSubMsg[] | null = null; + let contextPattern: PubSubMsg[] | null = null; + let callback; + + if (method === MethodTesting.Callback) { + contextExact = []; + contextPattern = []; + callback = newMessage; + } + + try { + // Setup PUBSUB for exact channels + pubSubExact = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set( + Object.keys(exactChannelsAndMessages), + ), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set(Object.keys(exactChannelsAndMessages)), + }, + callback, + contextExact, + ); + + [listeningClientExact, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSubExact, + ); + + // Setup PUBSUB for pattern channels + pubSubPattern = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Pattern]: new Set([PATTERN]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Pattern]: + new Set([PATTERN]), + }, + callback, + contextPattern, + ); + + [listeningClientPattern, clientDontCare] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSubPattern, + ); + + // Publish messages to all channels + for (const [channel, message] of Object.entries( + allChannelsAndMessages, + )) { + const result = await publishingClient.publish( + message, + channel, + ); + + if (clusterMode) { + expect(result).toEqual(1); + } + } + + // Allow the messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + let length = Object.keys(exactChannelsAndMessages).length; + + // Verify messages for exact PUBSUB + for (let index = 0; index < length; index++) { + const pubsubMsg = await getMessageByMethod( + method, + listeningClientExact, + contextExact, + index, + ); + expect( + pubsubMsg.channel in exactChannelsAndMessages, + ).toBeTruthy(); + expect(pubsubMsg.message).toEqual( + exactChannelsAndMessages[pubsubMsg.channel], + ); + expect(pubsubMsg.pattern).toBeNull(); + delete exactChannelsAndMessages[pubsubMsg.channel]; + } + + // Check that we received all exact messages + expect(Object.keys(exactChannelsAndMessages).length).toEqual(0); + + length = Object.keys(patternChannelsAndMessages).length; + + // Verify messages for pattern PUBSUB + for (let index = 0; index < length; index++) { + const pubsubMsg = await getMessageByMethod( + method, + listeningClientPattern, + contextPattern, + index, + ); + expect( + pubsubMsg.channel in patternChannelsAndMessages, + ).toBeTruthy(); + expect(pubsubMsg.message).toEqual( + patternChannelsAndMessages[pubsubMsg.channel], + ); + expect(pubsubMsg.pattern).toEqual(PATTERN); + delete patternChannelsAndMessages[pubsubMsg.channel]; + } + + // Check that we received all pattern messages + expect(Object.keys(patternChannelsAndMessages).length).toEqual( + 0, + ); + + // Assert no messages are left unread + await checkNoMessagesLeft( + method, + listeningClientExact, + contextExact, + NUM_CHANNELS, + ); + await checkNoMessagesLeft( + method, + listeningClientPattern, + contextPattern, + NUM_CHANNELS, + ); + } finally { + // Cleanup clients + if (listeningClientExact) { + await clientCleanup( + listeningClientExact, + clusterMode ? pubSubExact! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + + if (listeningClientPattern) { + await clientCleanup( + listeningClientPattern, + clusterMode ? pubSubPattern! : undefined, + ); + } + + if (clientDontCare) { + await clientCleanup(clientDontCare); + } + } + }, + TIMEOUT, + ); + + /** + * Tests combined exact, pattern, and sharded PUBSUB with one client. + * + * This test verifies that a single client can correctly handle exact, pattern, and sharded PUBSUB + * subscriptions. It covers the following scenarios: + * - Subscribing to multiple channels with exact names and verifying message reception. + * - Subscribing to channels using a pattern and verifying message reception. + * - Subscribing to channels using a sharded subscription and verifying message reception. + * - Ensuring that messages are correctly published and received using different retrieval methods (async, sync, callback). + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + * @param method - Specifies the method of PUBSUB subscription (Async, Sync, Callback). + */ + it.each([ + [true, MethodTesting.Async], + [true, MethodTesting.Sync], + [true, MethodTesting.Callback], + ])( + "pubsub combined exact, pattern, and sharded test_%p_%p", + async (clusterMode, method) => { + const minVersion = "7.0.0"; + + if (cmeCluster.checkIfServerVersionLessThan(minVersion)) return; + + const NUM_CHANNELS = 256; + const PATTERN = "{{pattern}}:*"; + const SHARD_PREFIX = "{same-shard}"; + + // Create dictionaries of channels and their corresponding messages + const exactChannelsAndMessages: Record = {}; + const patternChannelsAndMessages: Record = {}; + const shardedChannelsAndMessages: Record = {}; + + for (let i = 0; i < NUM_CHANNELS; i++) { + const exactChannel = `{{channel}}:${uuidv4()}`; + const patternChannel = `{{pattern}}:${uuidv4()}`; + const shardedChannel = `${SHARD_PREFIX}:${uuidv4()}`; + exactChannelsAndMessages[exactChannel] = uuidv4(); + patternChannelsAndMessages[patternChannel] = uuidv4(); + shardedChannelsAndMessages[shardedChannel] = uuidv4(); + } + + const publishResponse = 1; + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let listeningClient: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + let context: PubSubMsg[] | null = null; + let callback; + + if (method === MethodTesting.Callback) { + context = []; + callback = newMessage; + } + + try { + // Setup PUBSUB for exact, pattern, and sharded channels + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set( + Object.keys(exactChannelsAndMessages), + ), + [GlideClusterClientConfiguration.PubSubChannelModes + .Pattern]: new Set([PATTERN]), + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set( + Object.keys(shardedChannelsAndMessages), + ), + }, + {}, + callback, + context, + ); + + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + // Publish messages to exact and pattern channels + for (const [channel, message] of Object.entries({ + ...exactChannelsAndMessages, + ...patternChannelsAndMessages, + })) { + const result = await publishingClient.publish( + message, + channel, + ); + expect(result).toEqual(publishResponse); + } + + // Publish sharded messages + for (const [channel, message] of Object.entries( + shardedChannelsAndMessages, + )) { + const result = await ( + publishingClient as GlideClusterClient + ).publish(message, channel, true); + expect(result).toEqual(publishResponse); + } + + // Allow messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const allChannelsAndMessages = { + ...exactChannelsAndMessages, + ...patternChannelsAndMessages, + ...shardedChannelsAndMessages, + }; + + // Check if all messages are received correctly + for (let index = 0; index < NUM_CHANNELS * 3; index++) { + const pubsubMsg: PubSubMsg = await getMessageByMethod( + method, + listeningClient, + context, + index, + ); + const pattern = + pubsubMsg.channel in patternChannelsAndMessages + ? PATTERN + : null; + expect( + pubsubMsg.channel in allChannelsAndMessages, + ).toBeTruthy(); + expect(pubsubMsg.message).toEqual( + allChannelsAndMessages[pubsubMsg.channel], + ); + expect(pubsubMsg.pattern).toEqual(pattern); + delete allChannelsAndMessages[pubsubMsg.channel]; + } + + // Assert we received all messages + expect(Object.keys(allChannelsAndMessages).length).toEqual(0); + + await checkNoMessagesLeft( + method, + listeningClient, + context, + NUM_CHANNELS * 3, + ); + } finally { + // Cleanup clients + if (listeningClient) { + await clientCleanup( + listeningClient, + clusterMode ? pubSub! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + + /** + * Tests combined exact, pattern, and sharded PUBSUB with multiple clients, one for each subscription. + * + * This test verifies that separate clients can correctly handle exact, pattern, and sharded PUBSUB + * subscriptions. It covers the following scenarios: + * - Subscribing to multiple channels with exact names and verifying message reception. + * - Subscribing to channels using a pattern and verifying message reception. + * - Subscribing to channels using a sharded subscription and verifying message reception. + * - Ensuring that messages are correctly published and received using different retrieval methods (async, sync, callback). + * - Verifying that no messages are left unread. + * - Properly unsubscribing from all channels to avoid interference with other tests. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + * @param method - Specifies the method of PUBSUB subscription (Async, Sync, Callback). + */ + it.each([ + [true, MethodTesting.Async], + [true, MethodTesting.Sync], + [true, MethodTesting.Callback], + ])( + "pubsub combined exact, pattern, and sharded multi-client test_%p_%p", + async (clusterMode, method) => { + const minVersion = "7.0.0"; + + if (cmeCluster.checkIfServerVersionLessThan(minVersion)) return; + + const NUM_CHANNELS = 256; + const PATTERN = "{{pattern}}:*"; + const SHARD_PREFIX = "{same-shard}"; + + // Create dictionaries of channels and their corresponding messages + const exactChannelsAndMessages: Record = {}; + const patternChannelsAndMessages: Record = {}; + const shardedChannelsAndMessages: Record = {}; + + for (let i = 0; i < NUM_CHANNELS; i++) { + const exactChannel = `{{channel}}:${uuidv4()}`; + const patternChannel = `{{pattern}}:${uuidv4()}`; + const shardedChannel = `${SHARD_PREFIX}:${uuidv4()}`; + exactChannelsAndMessages[exactChannel] = uuidv4(); + patternChannelsAndMessages[patternChannel] = uuidv4(); + shardedChannelsAndMessages[shardedChannel] = uuidv4(); + } + + const publishResponse = 1; + let listeningClientExact: TGlideClient | null = null; + let listeningClientPattern: TGlideClient | null = null; + let listeningClientSharded: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + + let pubSubExact: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let pubSubPattern: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let pubSubSharded: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + + let context: PubSubMsg[] | null = null; + let callback; + + const callbackMessagesExact: PubSubMsg[] = []; + const callbackMessagesPattern: PubSubMsg[] = []; + const callbackMessagesSharded: PubSubMsg[] = []; + + if (method === MethodTesting.Callback) { + callback = newMessage; + context = callbackMessagesExact; + } + + try { + // Setup PUBSUB for exact channels + pubSubExact = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set( + Object.keys(exactChannelsAndMessages), + ), + }, + {}, + callback, + context, + ); + + [listeningClientExact, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSubExact, + ); + + if (method === MethodTesting.Callback) { + context = callbackMessagesPattern; + } + + // Setup PUBSUB for pattern channels + pubSubPattern = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Pattern]: new Set([PATTERN]), + }, + {}, + callback, + context, + ); + + if (method === MethodTesting.Callback) { + context = callbackMessagesSharded; + } + + pubSubSharded = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set( + Object.keys(shardedChannelsAndMessages), + ), + }, + {}, + callback, + context, + ); + + [listeningClientPattern, listeningClientSharded] = + await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSubPattern, + pubSubSharded, + ); + + // Publish messages to exact and pattern channels + for (const [channel, message] of Object.entries({ + ...exactChannelsAndMessages, + ...patternChannelsAndMessages, + })) { + const result = await publishingClient.publish( + message, + channel, + ); + expect(result).toEqual(publishResponse); + } + + // Publish sharded messages to all channels + for (const [channel, message] of Object.entries( + shardedChannelsAndMessages, + )) { + const result = await ( + publishingClient as GlideClusterClient + ).publish(message, channel, true); + expect(result).toEqual(publishResponse); + } + + // Allow messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Verify messages for exact PUBSUB + for (let index = 0; index < NUM_CHANNELS; index++) { + const pubsubMsg = await getMessageByMethod( + method, + listeningClientExact, + callbackMessagesExact, + index, + ); + expect( + pubsubMsg.channel in exactChannelsAndMessages, + ).toBeTruthy(); + expect(pubsubMsg.message).toEqual( + exactChannelsAndMessages[pubsubMsg.channel], + ); + expect(pubsubMsg.pattern).toBeNull(); + delete exactChannelsAndMessages[pubsubMsg.channel]; + } + + // Check that we received all messages for exact PUBSUB + expect(Object.keys(exactChannelsAndMessages).length).toEqual(0); + + // Verify messages for pattern PUBSUB + for (let index = 0; index < NUM_CHANNELS; index++) { + const pubsubMsg = await getMessageByMethod( + method, + listeningClientPattern, + callbackMessagesPattern, + index, + ); + expect( + pubsubMsg.channel in patternChannelsAndMessages, + ).toBeTruthy(); + expect(pubsubMsg.message).toEqual( + patternChannelsAndMessages[pubsubMsg.channel], + ); + expect(pubsubMsg.pattern).toEqual(PATTERN); + delete patternChannelsAndMessages[pubsubMsg.channel]; + } + + // Check that we received all messages for pattern PUBSUB + expect(Object.keys(patternChannelsAndMessages).length).toEqual( + 0, + ); + + // Verify messages for sharded PUBSUB + for (let index = 0; index < NUM_CHANNELS; index++) { + const pubsubMsg = await getMessageByMethod( + method, + listeningClientSharded, + callbackMessagesSharded, + index, + ); + expect( + pubsubMsg.channel in shardedChannelsAndMessages, + ).toBeTruthy(); + expect(pubsubMsg.message).toEqual( + shardedChannelsAndMessages[pubsubMsg.channel], + ); + expect(pubsubMsg.pattern).toBeNull(); + delete shardedChannelsAndMessages[pubsubMsg.channel]; + } + + // Check that we received all messages for sharded PUBSUB + expect(Object.keys(shardedChannelsAndMessages).length).toEqual( + 0, + ); + + await checkNoMessagesLeft( + method, + listeningClientExact, + callbackMessagesExact, + NUM_CHANNELS, + ); + await checkNoMessagesLeft( + method, + listeningClientPattern, + callbackMessagesPattern, + NUM_CHANNELS, + ); + await checkNoMessagesLeft( + method, + listeningClientSharded, + callbackMessagesSharded, + NUM_CHANNELS, + ); + } finally { + // Cleanup clients + if (listeningClientExact) { + await clientCleanup( + listeningClientExact, + clusterMode ? pubSubExact! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + + if (listeningClientPattern) { + await clientCleanup( + listeningClientPattern, + clusterMode ? pubSubPattern! : undefined, + ); + } + + if (listeningClientSharded) { + await clientCleanup( + listeningClientSharded, + clusterMode ? pubSubSharded! : undefined, + ); + } + } + }, + TIMEOUT, + ); + + /** + * Tests combined PUBSUB with different channel modes using the same channel name. + * One publishing client, three listening clients, one for each mode. + * + * This test verifies that separate clients can correctly handle subscriptions for exact, pattern, and sharded channels with the same name. + * It covers the following scenarios: + * - Subscribing to an exact channel and verifying message reception. + * - Subscribing to a pattern channel and verifying message reception. + * - Subscribing to a sharded channel and verifying message reception. + * - Ensuring that messages are correctly published and received using different retrieval methods (async, sync, callback). + * - Verifying that no messages are left unread. + * - Properly unsubscribing from all channels to avoid interference with other tests. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + * @param method - Specifies the method of PUBSUB subscription (Async, Sync, Callback). + */ + it.each([ + [true, MethodTesting.Async], + [true, MethodTesting.Sync], + [true, MethodTesting.Callback], + ])( + "pubsub combined different channels with same name test_%p_%p", + async (clusterMode, method) => { + const minVersion = "7.0.0"; + + if (cmeCluster.checkIfServerVersionLessThan(minVersion)) return; + + const CHANNEL_NAME = "same-channel-name"; + const MESSAGE_EXACT = uuidv4(); + const MESSAGE_PATTERN = uuidv4(); + const MESSAGE_SHARDED = uuidv4(); + + let listeningClientExact: TGlideClient | null = null; + let listeningClientPattern: TGlideClient | null = null; + let listeningClientSharded: TGlideClient | null = null; + let publishingClient: TGlideClient | null = null; + + let pubSubExact: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let pubSubPattern: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let pubSubSharded: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + + let context: PubSubMsg[] | null = null; + let callback; + + const callbackMessagesExact: PubSubMsg[] = []; + const callbackMessagesPattern: PubSubMsg[] = []; + const callbackMessagesSharded: PubSubMsg[] = []; + + if (method === MethodTesting.Callback) { + callback = newMessage; + context = callbackMessagesExact; + } + + try { + // Setup PUBSUB for exact channel + pubSubExact = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([CHANNEL_NAME]), + }, + {}, + callback, + context, + ); + + [listeningClientExact, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSubExact, + ); + + // Setup PUBSUB for pattern channel + if (method === MethodTesting.Callback) { + context = callbackMessagesPattern; + } + + pubSubPattern = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Pattern]: new Set([CHANNEL_NAME]), + }, + {}, + callback, + context, + ); + + if (method === MethodTesting.Callback) { + context = callbackMessagesSharded; + } + + pubSubSharded = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set([CHANNEL_NAME]), + }, + {}, + callback, + context, + ); + + [listeningClientPattern, listeningClientSharded] = + await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSubPattern, + pubSubSharded, + ); + + // Publish messages to each channel + expect( + await publishingClient.publish(MESSAGE_EXACT, CHANNEL_NAME), + ).toEqual(2); + expect( + await publishingClient.publish( + MESSAGE_PATTERN, + CHANNEL_NAME, + ), + ).toEqual(2); + expect( + await (publishingClient as GlideClusterClient).publish( + MESSAGE_SHARDED, + CHANNEL_NAME, + true, + ), + ).toEqual(1); + + // Allow messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Verify message for exact and pattern PUBSUB + for (const [client, callback, pattern] of [ + [listeningClientExact, callbackMessagesExact, null], + [ + listeningClientPattern, + callbackMessagesPattern, + CHANNEL_NAME, + ], + ] as [TGlideClient, PubSubMsg[], string | null][]) { + const pubsubMsg = await getMessageByMethod( + method, + client, + callback, + 0, + ); + const pubsubMsg2 = await getMessageByMethod( + method, + client, + callback, + 1, + ); + + expect(pubsubMsg.message).not.toEqual(pubsubMsg2.message); + expect([MESSAGE_PATTERN, MESSAGE_EXACT]).toContain( + pubsubMsg.message, + ); + expect([MESSAGE_PATTERN, MESSAGE_EXACT]).toContain( + pubsubMsg2.message, + ); + expect(pubsubMsg.channel).toEqual(CHANNEL_NAME); + expect(pubsubMsg2.channel).toEqual(CHANNEL_NAME); + expect(pubsubMsg.pattern).toEqual(pattern); + expect(pubsubMsg2.pattern).toEqual(pattern); + } + + // Verify message for sharded PUBSUB + const pubsubMsgSharded = await getMessageByMethod( + method, + listeningClientSharded, + callbackMessagesSharded, + 0, + ); + expect(pubsubMsgSharded.message).toEqual(MESSAGE_SHARDED); + expect(pubsubMsgSharded.channel).toEqual(CHANNEL_NAME); + expect(pubsubMsgSharded.pattern).toBeNull(); + + await checkNoMessagesLeft( + method, + listeningClientExact, + callbackMessagesExact, + 2, + ); + await checkNoMessagesLeft( + method, + listeningClientPattern, + callbackMessagesPattern, + 2, + ); + await checkNoMessagesLeft( + method, + listeningClientSharded, + callbackMessagesSharded, + 1, + ); + } finally { + // Cleanup clients + if (listeningClientExact) { + await clientCleanup( + listeningClientExact, + clusterMode ? pubSubExact! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + + if (listeningClientPattern) { + await clientCleanup( + listeningClientPattern, + clusterMode ? pubSubPattern! : undefined, + ); + } + + if (listeningClientSharded) { + await clientCleanup( + listeningClientSharded, + clusterMode ? pubSubSharded! : undefined, + ); + } + } + }, + TIMEOUT, + ); + + /** + * Tests PUBSUB with two publishing clients using the same channel name. + * One client uses pattern subscription, the other uses exact. + * The clients publish messages to each other and to themselves. + * + * This test verifies that two separate clients can correctly publish to and handle subscriptions + * for exact and pattern channels with the same name. It covers the following scenarios: + * - Subscribing to an exact channel and verifying message reception. + * - Subscribing to a pattern channel and verifying message reception. + * - Ensuring that messages are correctly published and received using different retrieval methods (async, sync, callback). + * - Verifying that no messages are left unread. + * - Properly unsubscribing from all channels to avoid interference with other tests. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + * @param method - Specifies the method of PUBSUB subscription (Async, Sync, Callback). + */ + it.each(testCases)( + "pubsub two publishing clients same name test_%p_%p", + async (clusterMode, method) => { + const CHANNEL_NAME = "channel-name"; + const MESSAGE_EXACT = uuidv4(); + const MESSAGE_PATTERN = uuidv4(); + + let clientExact: TGlideClient | null = null; + let clientPattern: TGlideClient | null = null; + + let pubSubExact: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let pubSubPattern: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + + let contextExact: PubSubMsg[] | null = null; + let contextPattern: PubSubMsg[] | null = null; + let callback; + + const callbackMessagesExact: PubSubMsg[] = []; + const callbackMessagesPattern: PubSubMsg[] = []; + + if (method === MethodTesting.Callback) { + callback = newMessage; + contextExact = callbackMessagesExact; + contextPattern = callbackMessagesPattern; + } + + try { + // Setup PUBSUB for exact channel + pubSubExact = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([CHANNEL_NAME]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set([CHANNEL_NAME]), + }, + callback, + contextExact, + ); + + // Setup PUBSUB for pattern channels + pubSubPattern = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Pattern]: new Set([CHANNEL_NAME]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Pattern]: + new Set([CHANNEL_NAME]), + }, + callback, + contextPattern, + ); + + [clientExact, clientPattern] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSubExact, + pubSubPattern, + ); + + // Publish messages to each channel - both clients publishing + for (const msg of [MESSAGE_EXACT, MESSAGE_PATTERN]) { + const result = await clientPattern.publish( + msg, + CHANNEL_NAME, + ); + + if (clusterMode) { + expect(result).toEqual(2); + } + } + + // Allow messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Verify message for exact and pattern PUBSUB + for (const [client, callback, pattern] of [ + [clientExact, callbackMessagesExact, null], + [clientPattern, callbackMessagesPattern, CHANNEL_NAME], + ] as [TGlideClient, PubSubMsg[], string | null][]) { + const pubsubMsg = await getMessageByMethod( + method, + client, + callback, + 0, + ); + const pubsubMsg2 = await getMessageByMethod( + method, + client, + callback, + 1, + ); + + expect(pubsubMsg.message).not.toEqual(pubsubMsg2.message); + expect([MESSAGE_PATTERN, MESSAGE_EXACT]).toContain( + pubsubMsg.message, + ); + expect([MESSAGE_PATTERN, MESSAGE_EXACT]).toContain( + pubsubMsg2.message, + ); + expect(pubsubMsg.channel).toEqual(CHANNEL_NAME); + expect(pubsubMsg2.channel).toEqual(CHANNEL_NAME); + expect(pubsubMsg.pattern).toEqual(pattern); + expect(pubsubMsg2.pattern).toEqual(pattern); + } + + await checkNoMessagesLeft( + method, + clientPattern, + callbackMessagesPattern, + 2, + ); + await checkNoMessagesLeft( + method, + clientExact, + callbackMessagesExact, + 2, + ); + } finally { + // Cleanup clients + if (clientExact) { + await clientCleanup( + clientExact, + clusterMode ? pubSubExact! : undefined, + ); + } + + if (clientPattern) { + await clientCleanup( + clientPattern, + clusterMode ? pubSubPattern! : undefined, + ); + } + } + }, + TIMEOUT, + ); + + /** + * Tests PUBSUB with 3 publishing clients using the same channel name. + * One client uses pattern subscription, one uses exact, and one uses sharded. + * + * This test verifies that 3 separate clients can correctly publish to and handle subscriptions + * for exact, sharded, and pattern channels with the same name. It covers the following scenarios: + * - Subscribing to an exact channel and verifying message reception. + * - Subscribing to a pattern channel and verifying message reception. + * - Subscribing to a sharded channel and verifying message reception. + * - Ensuring that messages are correctly published and received using different retrieval methods (async, sync, callback). + * - Verifying that no messages are left unread. + * - Properly unsubscribing from all channels to avoid interference with other tests. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + * @param method - Specifies the method of PUBSUB subscription (Async, Sync, Callback). + */ + it.each([ + [true, MethodTesting.Async], + [true, MethodTesting.Sync], + [true, MethodTesting.Callback], + ])( + "pubsub three publishing clients same name with sharded test_%p_%p", + async (clusterMode, method) => { + const minVersion = "7.0.0"; + + if (cmeCluster.checkIfServerVersionLessThan(minVersion)) return; + + const CHANNEL_NAME = "same-channel-name"; + const MESSAGE_EXACT = uuidv4(); + const MESSAGE_PATTERN = uuidv4(); + const MESSAGE_SHARDED = uuidv4(); + + let clientExact: TGlideClient | null = null; + let clientPattern: TGlideClient | null = null; + let clientSharded: TGlideClient | null = null; + let clientDontCare: TGlideClient | null = null; + + let pubSubExact: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let pubSubPattern: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let pubSubSharded: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + + let contextExact: PubSubMsg[] | null = null; + let contextPattern: PubSubMsg[] | null = null; + let contextSharded: PubSubMsg[] | null = null; + let callback; + + const callbackMessagesExact: PubSubMsg[] = []; + const callbackMessagesPattern: PubSubMsg[] = []; + const callbackMessagesSharded: PubSubMsg[] = []; + + if (method === MethodTesting.Callback) { + callback = newMessage; + contextExact = callbackMessagesExact; + contextPattern = callbackMessagesPattern; + contextSharded = callbackMessagesSharded; + } + + try { + // Setup PUBSUB for exact channel + pubSubExact = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([CHANNEL_NAME]), + }, + {}, + callback, + contextExact, + ); + + // Setup PUBSUB for pattern channels + pubSubPattern = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Pattern]: new Set([CHANNEL_NAME]), + }, + {}, + callback, + contextPattern, + ); + + // Setup PUBSUB for sharded channels + pubSubSharded = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set([CHANNEL_NAME]), + }, + {}, + callback, + contextSharded, + ); + + [clientExact, clientPattern] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSubExact, + pubSubPattern, + ); + + [clientSharded, clientDontCare] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSubSharded, + ); + + // Publish messages to each channel - all clients publishing + const publishResponse = 2; + + expect( + await clientPattern.publish(MESSAGE_EXACT, CHANNEL_NAME), + ).toEqual(publishResponse); + + expect( + await clientSharded.publish(MESSAGE_PATTERN, CHANNEL_NAME), + ).toEqual(publishResponse); + + expect( + await (clientExact as GlideClusterClient).publish( + MESSAGE_SHARDED, + CHANNEL_NAME, + true, + ), + ).toEqual(1); + + // Allow messages to propagate + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Verify message for exact and pattern PUBSUB + for (const [client, callback, pattern] of [ + [clientExact, callbackMessagesExact, null], + [clientPattern, callbackMessagesPattern, CHANNEL_NAME], + ] as [TGlideClient, PubSubMsg[], string | null][]) { + const pubsubMsg = await getMessageByMethod( + method, + client, + callback, + 0, + ); + const pubsubMsg2 = await getMessageByMethod( + method, + client, + callback, + 1, + ); + + expect(pubsubMsg.message).not.toEqual(pubsubMsg2.message); + expect([MESSAGE_PATTERN, MESSAGE_EXACT]).toContain( + pubsubMsg.message, + ); + expect([MESSAGE_PATTERN, MESSAGE_EXACT]).toContain( + pubsubMsg2.message, + ); + expect(pubsubMsg.channel).toEqual(CHANNEL_NAME); + expect(pubsubMsg2.channel).toEqual(CHANNEL_NAME); + expect(pubsubMsg.pattern).toEqual(pattern); + expect(pubsubMsg2.pattern).toEqual(pattern); + } + + const shardedMsg = await getMessageByMethod( + method, + clientSharded, + callbackMessagesSharded, + 0, + ); + + expect(shardedMsg.message).toEqual(MESSAGE_SHARDED); + expect(shardedMsg.channel).toEqual(CHANNEL_NAME); + expect(shardedMsg.pattern).toBeNull(); + + await checkNoMessagesLeft( + method, + clientPattern, + callbackMessagesPattern, + 2, + ); + await checkNoMessagesLeft( + method, + clientExact, + callbackMessagesExact, + 2, + ); + await checkNoMessagesLeft( + method, + clientSharded, + callbackMessagesSharded, + 1, + ); + } finally { + // Cleanup clients + if (clientExact) { + await clientCleanup( + clientExact, + clusterMode ? pubSubExact! : undefined, + ); + } + + if (clientPattern) { + await clientCleanup( + clientPattern, + clusterMode ? pubSubPattern! : undefined, + ); + } + + if (clientSharded) { + await clientCleanup( + clientSharded, + clusterMode ? pubSubSharded! : undefined, + ); + } + + if (clientDontCare) { + await clientCleanup(clientDontCare, undefined); + } + } + }, + TIMEOUT, + ); + describe.skip("pubsub max size message test", () => { + const generateLargeMessage = (char: string, size: number): string => { + let message = ""; + + for (let i = 0; i < size; i++) { + message += char; + } + + return message; + }; + + /** + * Tests publishing and receiving maximum size messages in PUBSUB. + * + * This test verifies that very large messages (512MB - BulkString max size) can be published and received + * correctly in both cluster and standalone modes. It ensures that the PUBSUB system + * can handle maximum size messages without errors and that async and sync message + * retrieval methods can coexist and function correctly. + * + * The test covers the following scenarios: + * - Setting up PUBSUB subscription for a specific channel. + * - Publishing two maximum size messages to the channel. + * - Verifying that the messages are received correctly using both async and sync methods. + * - Ensuring that no additional messages are left after the expected messages are received. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true, false])( + "test pubsub exact max size message_%p", + async (clusterMode) => { + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + + let listeningClient: TGlideClient | undefined; + let publishingClient: TGlideClient | undefined; + + const channel = uuidv4(); + + const message = generateLargeMessage("1", 512 * 1024 * 1024); // 512MB message + const message2 = generateLargeMessage("2", 512 * 1024 * 10); + + try { + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([channel]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set([channel]), + }, + ); + + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + let result = await publishingClient.publish( + message, + channel, + ); + + if (clusterMode) { + expect(result).toEqual(1); + } + + result = await publishingClient.publish(message2, channel); + + if (clusterMode) { + expect(result).toEqual(1); + } + + // Allow the message to propagate + await new Promise((resolve) => setTimeout(resolve, 15000)); + + const asyncMsg = await listeningClient.getPubSubMessage(); + expect(asyncMsg.message).toEqual(Buffer.from(message)); + expect(asyncMsg.channel).toEqual(Buffer.from(channel)); + expect(asyncMsg.pattern).toBeNull(); + + const syncMsg = listeningClient.tryGetPubSubMessage(); + expect(syncMsg).not.toBeNull(); + expect(syncMsg!.message).toEqual(Buffer.from(message2)); + expect(syncMsg!.channel).toEqual(Buffer.from(channel)); + expect(syncMsg!.pattern).toBeNull(); + + // Assert there are no messages to read + await checkNoMessagesLeft( + MethodTesting.Async, + listeningClient, + ); + expect(listeningClient.tryGetPubSubMessage()).toBeNull(); + } finally { + if (listeningClient) { + await clientCleanup( + listeningClient, + clusterMode ? pubSub! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + + /** + * Tests publishing and receiving maximum size messages in sharded PUBSUB. + * + * This test verifies that very large messages (512MB - BulkString max size) can be published and received + * correctly. It ensures that the PUBSUB system + * can handle maximum size messages without errors and that async and sync message + * retrieval methods can coexist and function correctly. + * + * The test covers the following scenarios: + * - Setting up PUBSUB subscription for a specific sharded channel. + * - Publishing two maximum size messages to the channel. + * - Verifying that the messages are received correctly using both async and sync methods. + * - Ensuring that no additional messages are left after the expected messages are received. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true])( + "test pubsub sharded max size message_%p", + async (clusterMode) => { + if (cmeCluster.checkIfServerVersionLessThan("7.0.0")) return; + + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + + let listeningClient: TGlideClient | undefined; + let publishingClient: TGlideClient | undefined; + const channel = uuidv4(); + + const message = generateLargeMessage("1", 512 * 1024 * 1024); // 512MB message + const message2 = generateLargeMessage("2", 512 * 1024 * 1024); // 512MB message + + try { + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set([channel]), + }, + {}, + ); + + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + expect( + await (publishingClient as GlideClusterClient).publish( + Buffer.from(message), + channel, + true, + ), + ).toEqual(1); + + expect( + await (publishingClient as GlideClusterClient).publish( + message2, + Buffer.from(channel), + true, + ), + ).toEqual(1); + + // Allow the message to propagate + await new Promise((resolve) => setTimeout(resolve, 15000)); + + const asyncMsg = await listeningClient.getPubSubMessage(); + const syncMsg = listeningClient.tryGetPubSubMessage(); + expect(syncMsg).not.toBeNull(); + + expect(asyncMsg.message).toEqual(Buffer.from(message)); + expect(asyncMsg.channel).toEqual(Buffer.from(channel)); + expect(asyncMsg.pattern).toBeNull(); + + expect(syncMsg!.message).toEqual(Buffer.from(message2)); + expect(syncMsg!.channel).toEqual(Buffer.from(channel)); + expect(syncMsg!.pattern).toBeNull(); + + // Assert there are no messages to read + await checkNoMessagesLeft( + MethodTesting.Async, + listeningClient, + ); + expect(listeningClient.tryGetPubSubMessage()).toBeNull(); + } finally { + if (listeningClient) { + await clientCleanup( + listeningClient, + clusterMode ? pubSub! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + + /** + * Tests publishing and receiving maximum size messages in exact PUBSUB with callback method. + * + * This test verifies that very large messages (512MB - BulkString max size) can be published and received + * correctly in both cluster and standalone modes. It ensures that the PUBSUB system + * can handle maximum size messages without errors and that the callback message + * retrieval method works as expected. + * + * The test covers the following scenarios: + * - Setting up PUBSUB subscription for a specific channel with a callback. + * - Publishing a maximum size message to the channel. + * - Verifying that the message is received correctly using the callback method. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true, false])( + "test pubsub exact max size message callback_%p", + async (clusterMode) => { + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + + let listeningClient: TGlideClient | undefined; + let publishingClient: TGlideClient | undefined; + const channel = uuidv4(); + + const message = generateLargeMessage("0", 12 * 1024 * 1024); // 12MB message + + try { + const callbackMessages: PubSubMsg[] = []; + const callback = newMessage; + + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([channel]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set([channel]), + }, + callback, + ); + + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + const result = await publishingClient.publish( + message, + channel, + ); + + if (clusterMode) { + expect(result).toEqual(1); + } + + // Allow the message to propagate + await new Promise((resolve) => setTimeout(resolve, 15000)); + + expect(callbackMessages.length).toEqual(1); + + expect(callbackMessages[0].message).toEqual( + Buffer.from(message), + ); + expect(callbackMessages[0].channel).toEqual( + Buffer.from(channel), + ); + expect(callbackMessages[0].pattern).toBeNull(); + // Assert no messages left + expect(callbackMessages.length).toEqual(1); + } finally { + if (listeningClient) { + await clientCleanup( + listeningClient, + clusterMode ? pubSub! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + + /** + * Tests publishing and receiving maximum size messages in sharded PUBSUB with callback method. + * + * This test verifies that very large messages (512MB - BulkString max size) can be published and received + * correctly. It ensures that the PUBSUB system + * can handle maximum size messages without errors and that callback + * retrieval methods can coexist and function correctly. + * + * The test covers the following scenarios: + * - Setting up PUBSUB subscription for a specific sharded channel. + * - Publishing a maximum size message to the channel. + * - Verifying that the messages are received correctly using callbacl method. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true])( + "test pubsub sharded max size message callback_%p", + async (clusterMode) => { + if (cmeCluster.checkIfServerVersionLessThan("7.0.0")) return; + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + + let listeningClient: TGlideClient | undefined; + let publishingClient: TGlideClient | undefined; + const channel = uuidv4(); + + const message = generateLargeMessage("0", 512 * 1024 * 1024); // 512MB message + + try { + const callbackMessages: PubSubMsg[] = []; + const callback = newMessage; + + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set([channel]), + }, + {}, + callback, + ); + + [listeningClient, publishingClient] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + expect( + await (publishingClient as GlideClusterClient).publish( + message, + channel, + true, + ), + ).toEqual(1); + + // Allow the message to propagate + await new Promise((resolve) => setTimeout(resolve, 15000)); + + expect(callbackMessages.length).toEqual(1); + + expect(callbackMessages[0].message).toEqual( + Buffer.from(message), + ); + expect(callbackMessages[0].channel).toEqual( + Buffer.from(channel), + ); + expect(callbackMessages[0].pattern).toBeNull(); + + // Assert no messages left + expect(callbackMessages.length).toEqual(1); + } finally { + if (listeningClient) { + await clientCleanup( + listeningClient, + clusterMode ? pubSub! : undefined, + ); + } + + if (publishingClient) { + await clientCleanup(publishingClient); + } + } + }, + TIMEOUT, + ); + }); + + /** + * Tests that creating a RESP2 client with PUBSUB raises a ConfigurationError. + * + * This test ensures that the system correctly prevents the creation of a PUBSUB client + * using the RESP2 protocol version, which is not supported. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true, false])( + "test pubsub resp2 raise an error_%p", + async (clusterMode) => { + const channel = uuidv4(); + + const pubSubExact = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes.Exact]: + new Set([channel]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set([channel]), + }, + ); + + await expect( + createClients( + clusterMode, + getOptions(clusterMode, ProtocolVersion.RESP2), + getOptions(clusterMode, ProtocolVersion.RESP2), + pubSubExact, + ), + ).rejects.toThrow(ConfigurationError); + }, + ); + + /** + * Tests that creating a PUBSUB client with context but without a callback raises a ConfigurationError. + * + * This test ensures that the system enforces the requirement of providing a callback when + * context is supplied, preventing misconfigurations. + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true, false])( + "test pubsub context with no callback raise error_%p", + async (clusterMode) => { + const channel = uuidv4(); + const context: PubSubMsg[] = []; + + const pubSubExact = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes.Exact]: + new Set([channel]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set([channel]), + }, + undefined, // No callback provided + context, + ); + + // Attempt to create clients, expecting an error + await expect( + createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSubExact, + ), + ).rejects.toThrow(ConfigurationError); + }, + TIMEOUT, + ); + + /** + * Tests the pubsubChannels command functionality. + * + * This test verifies that the pubsubChannels command correctly returns + * the active channels matching a specified pattern. + * + * It covers the following scenarios: + * - Checking that no channels exist initially + * - Subscribing to multiple channels + * - Retrieving all active channels without a pattern + * - Retrieving channels matching a specific pattern + * - Verifying that a non-matching pattern returns no channels + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true, false])( + "test pubsub channels_%p", + async (clusterMode) => { + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let client1: TGlideClient | null = null; + let client2: TGlideClient | null = null; + let client: TGlideClient | null = null; + + try { + const channel1 = "test_channel1"; + const channel2 = "test_channel2"; + const channel3 = "some_channel3"; + const pattern = "test_*"; + + if (clusterMode) { + client = await GlideClusterClient.createClient( + getOptions(clusterMode), + ); + } else { + client = await GlideClient.createClient( + getOptions(clusterMode), + ); + } + + // Assert no channels exists yet + expect(await client.pubsubChannels()).toEqual([]); + + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([channel1, channel2, channel3]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set([channel1, channel2, channel3]), + }, + ); + + [client1, client2] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + // Test pubsubChannels without pattern + const channels = await client2.pubsubChannels(); + expect(new Set(channels)).toEqual( + new Set([channel1, channel2, channel3]), + ); + + // Test pubsubChannels with pattern + const channelsWithPattern = await client2.pubsubChannels({ + pattern, + }); + expect(new Set(channelsWithPattern)).toEqual( + new Set([channel1, channel2]), + ); + + // Test with non-matching pattern + const nonMatchingChannels = await client2.pubsubChannels({ + pattern: "non_matching_*", + }); + expect(nonMatchingChannels.length).toBe(0); + } finally { + if (client1) { + await clientCleanup( + client1, + clusterMode ? pubSub! : undefined, + ); + } + + if (client2) { + await clientCleanup(client2); + } + + if (client) { + await clientCleanup(client); + } + } + }, + TIMEOUT, + ); + + /** + * Tests the pubsubNumPat command functionality. + * + * This test verifies that the pubsubNumPat command correctly returns + * the number of unique patterns that are subscribed to by clients. + * + * It covers the following scenarios: + * - Checking that no patterns exist initially + * - Subscribing to multiple patterns + * - Verifying the correct number of unique patterns + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true, false])( + "test pubsub numpat_%p", + async (clusterMode) => { + let pubSub: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let client1: TGlideClient | null = null; + let client2: TGlideClient | null = null; + let client: TGlideClient | null = null; + + try { + const pattern1 = "test_*"; + const pattern2 = "another_*"; + + // Create a client and check initial number of patterns + if (clusterMode) { + client = await GlideClusterClient.createClient( + getOptions(clusterMode), + ); + } else { + client = await GlideClient.createClient( + getOptions(clusterMode), + ); + } + + expect(await client.pubsubNumPat()).toBe(0); + + // Set up subscriptions with patterns + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Pattern]: new Set([pattern1, pattern2]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Pattern]: + new Set([pattern1, pattern2]), + }, + ); + + [client1, client2] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + const numPatterns = await client2.pubsubNumPat(); + expect(numPatterns).toBe(2); + } finally { + if (client1) { + await clientCleanup( + client1, + clusterMode ? pubSub! : undefined, + ); + } + + if (client2) { + await clientCleanup(client2); + } + + if (client) { + await clientCleanup(client); + } + } + }, + TIMEOUT, + ); + + /** + * Tests the pubsubNumSub command functionality. + * + * This test verifies that the pubsubNumSub command correctly returns + * the number of subscribers for specified channels. + * + * It covers the following scenarios: + * - Checking that no subscribers exist initially + * - Creating multiple clients with different channel subscriptions + * - Verifying the correct number of subscribers for each channel + * - Testing pubsubNumSub with no channels specified + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true, false])( + "test pubsub numsub_%p", + async (clusterMode) => { + let pubSub1: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let pubSub2: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let pubSub3: + | GlideClusterClientConfiguration.PubSubSubscriptions + | GlideClientConfiguration.PubSubSubscriptions + | null = null; + let client1: TGlideClient | null = null; + let client2: TGlideClient | null = null; + let client3: TGlideClient | null = null; + let client4: TGlideClient | null = null; + let client: TGlideClient | null = null; + + try { + const channel1 = "test_channel1"; + const channel2 = "test_channel2"; + const channel3 = "test_channel3"; + const channel4 = "test_channel4"; + + // Set up subscriptions + pubSub1 = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([channel1, channel2, channel3]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set([channel1, channel2, channel3]), + }, + ); + pubSub2 = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([channel2, channel3]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set([channel2, channel3]), + }, + ); + pubSub3 = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([channel3]), + }, + { + [GlideClientConfiguration.PubSubChannelModes.Exact]: + new Set([channel3]), + }, + ); + + // Create a client and check initial subscribers + if (clusterMode) { + client = await GlideClusterClient.createClient( + getOptions(clusterMode), + ); + } else { + client = await GlideClient.createClient( + getOptions(clusterMode), + ); + } + + expect( + await client.pubsubNumSub([channel1, channel2, channel3]), + ).toEqual({ + [channel1]: 0, + [channel2]: 0, + [channel3]: 0, + }); + + [client1, client2] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub1, + pubSub2, + ); + [client3, client4] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub3, + ); + + // Test pubsubNumsub + const subscribers = await client2.pubsubNumSub([ + channel1, + channel2, + channel3, + channel4, + ]); + expect(subscribers).toEqual({ + [channel1]: 1, + [channel2]: 2, + [channel3]: 3, + [channel4]: 0, + }); + + // Test pubsubNumsub with no channels + const emptySubscribers = await client2.pubsubNumSub(); + expect(emptySubscribers).toEqual({}); + } finally { + if (client1) { + await clientCleanup( + client1, + clusterMode ? pubSub1! : undefined, + ); + } + + if (client2) { + await clientCleanup( + client2, + clusterMode ? pubSub2! : undefined, + ); + } + + if (client3) { + await clientCleanup( + client3, + clusterMode ? pubSub3! : undefined, + ); + } + + if (client4) { + await clientCleanup(client4); + } + + if (client) { + await clientCleanup(client); + } + } + }, + TIMEOUT, + ); + + /** + * Tests the pubsubShardchannels command functionality. + * + * This test verifies that the pubsubShardchannels command correctly returns + * the active sharded channels matching a specified pattern. + * + * It covers the following scenarios: + * - Checking that no sharded channels exist initially + * - Subscribing to multiple sharded channels + * - Retrieving all active sharded channels without a pattern + * - Retrieving sharded channels matching a specific pattern + * - Verifying that a non-matching pattern returns no channels + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true])( + "test pubsub shardchannels_%p", + async (clusterMode) => { + const minVersion = "7.0.0"; + + if (cmeCluster.checkIfServerVersionLessThan(minVersion)) { + return; // Skip test if server version is less than required + } + + let pubSub: GlideClusterClientConfiguration.PubSubSubscriptions | null = + null; + let client1: TGlideClient | null = null; + let client2: TGlideClient | null = null; + let client: TGlideClient | null = null; + + try { + const channel1 = "test_shardchannel1"; + const channel2 = "test_shardchannel2"; + const channel3 = "some_shardchannel3"; + const pattern = "test_*"; + + client = await GlideClusterClient.createClient( + getOptions(clusterMode), + ); + + // Assert no sharded channels exist yet + expect(await client.pubsubShardChannels()).toEqual([]); + + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set([channel1, channel2, channel3]), + }, + {}, + ); + + [client1, client2] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + // Test pubsubShardchannels without pattern + const channels = await ( + client2 as GlideClusterClient + ).pubsubShardChannels(); + expect(new Set(channels)).toEqual( + new Set([channel1, channel2, channel3]), + ); + + // Test pubsubShardchannels with pattern + const channelsWithPattern = await ( + client2 as GlideClusterClient + ).pubsubShardChannels({ pattern }); + expect(new Set(channelsWithPattern)).toEqual( + new Set([channel1, channel2]), + ); + + // Test with non-matching pattern + const nonMatchingChannels = await ( + client2 as GlideClusterClient + ).pubsubShardChannels({ pattern: "non_matching_*" }); + expect(nonMatchingChannels).toEqual([]); + } finally { + if (client1) { + await clientCleanup(client1, pubSub ? pubSub : undefined); + } + + if (client2) { + await clientCleanup(client2); + } + + if (client) { + await clientCleanup(client); + } + } + }, + TIMEOUT, + ); + + /** + * Tests the pubsubShardnumsub command functionality. + * + * This test verifies that the pubsubShardnumsub command correctly returns + * the number of subscribers for specified sharded channels. + * + * It covers the following scenarios: + * - Checking that no subscribers exist initially for sharded channels + * - Creating multiple clients with different sharded channel subscriptions + * - Verifying the correct number of subscribers for each sharded channel + * - Testing pubsubShardnumsub with no channels specified + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true])( + "test pubsub shardnumsub_%p", + async (clusterMode) => { + let pubSub1: GlideClusterClientConfiguration.PubSubSubscriptions | null = + null; + let pubSub2: GlideClusterClientConfiguration.PubSubSubscriptions | null = + null; + let pubSub3: GlideClusterClientConfiguration.PubSubSubscriptions | null = + null; + let client1: TGlideClient | null = null; + let client2: TGlideClient | null = null; + let client3: TGlideClient | null = null; + let client4: TGlideClient | null = null; + let client: TGlideClient | null = null; + + try { + const channel1 = "test_shardchannel1"; + const channel2 = "test_shardchannel2"; + const channel3 = "test_shardchannel3"; + const channel4 = "test_shardchannel4"; + + // Set up subscriptions + pubSub1 = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set([channel1, channel2, channel3]), + }, + {}, + ); + pubSub2 = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set([channel2, channel3]), + }, + {}, + ); + pubSub3 = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set([channel3]), + }, + {}, + ); + + // Create a client and check initial subscribers + client = await GlideClusterClient.createClient( + getOptions(clusterMode), + ); + const minVersion = "7.0.0"; + + if (cmeCluster.checkIfServerVersionLessThan(minVersion)) { + return; // Skip test if server version is less than required + } + + expect( + await (client as GlideClusterClient).pubsubShardNumSub([ + channel1, + channel2, + channel3, + ]), + ).toEqual({ + [channel1]: 0, + [channel2]: 0, + [channel3]: 0, + }); + + [client1, client2] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub1, + pubSub2, + ); + [client3, client4] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub3, + ); + + // Test pubsubShardnumsub + const subscribers = await ( + client4 as GlideClusterClient + ).pubsubShardNumSub([channel1, channel2, channel3, channel4]); + expect(subscribers).toEqual({ + [channel1]: 1, + [channel2]: 2, + [channel3]: 3, + [channel4]: 0, + }); + + // Test pubsubShardnumsub with no channels + const emptySubscribers = await ( + client4 as GlideClusterClient + ).pubsubShardNumSub(); + expect(emptySubscribers).toEqual({}); + } finally { + if (client1) { + await clientCleanup(client1, pubSub1 ? pubSub1 : undefined); + } + + if (client2) { + await clientCleanup(client2, pubSub2 ? pubSub2 : undefined); + } + + if (client3) { + await clientCleanup(client3, pubSub3 ? pubSub3 : undefined); + } + + if (client4) { + await clientCleanup(client4); + } + + if (client) { + await clientCleanup(client); + } + } + }, + TIMEOUT, + ); + + /** + * Tests that pubsubChannels doesn't return sharded channels and pubsubShardchannels + * doesn't return regular channels. + * + * This test verifies the separation between regular and sharded channels in PUBSUB operations. + * + * It covers the following scenarios: + * - Subscribing to both a regular channel and a sharded channel + * - Verifying that pubsubChannels only returns the regular channel + * - Verifying that pubsubShardchannels only returns the sharded channel + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true])( + "test pubsub channels and shardchannels separation_%p", + async (clusterMode) => { + let pubSub: GlideClusterClientConfiguration.PubSubSubscriptions | null = + null; + let client1: TGlideClient | null = null; + let client2: TGlideClient | null = null; + + try { + const regularChannel = "regular_channel"; + const shardChannel = "shard_channel"; + + pubSub = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([regularChannel]), + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set([shardChannel]), + }, + {}, + ); + + [client1, client2] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub, + ); + + const minVersion = "7.0.0"; + + if (cmeCluster.checkIfServerVersionLessThan(minVersion)) { + return; // Skip test if server version is less than required + } + + // Test pubsubChannels + const regularChannels = await client2.pubsubChannels(); + expect(regularChannels).toEqual([regularChannel]); + + // Test pubsubShardchannels + const shardChannels = await ( + client2 as GlideClusterClient + ).pubsubShardChannels(); + expect(shardChannels).toEqual([shardChannel]); + } finally { + if (client1) { + await clientCleanup(client1, pubSub ? pubSub : undefined); + } + + if (client2) { + await clientCleanup(client2); + } + } + }, + TIMEOUT, + ); + + /** + * Tests that pubsubNumSub doesn't count sharded channel subscribers and pubsubShardnumsub + * doesn't count regular channel subscribers. + * + * This test verifies the separation between regular and sharded channel subscribers in PUBSUB operations. + * + * It covers the following scenarios: + * - Subscribing to both a regular channel and a sharded channel with two clients + * - Verifying that pubsubNumSub only counts subscribers for the regular channel + * - Verifying that pubsubShardnumsub only counts subscribers for the sharded channel + * + * @param clusterMode - Indicates if the test should be run in cluster mode. + */ + it.each([true])( + "test pubsub numsub and shardnumsub separation_%p", + async (clusterMode) => { + let pubSub1: GlideClusterClientConfiguration.PubSubSubscriptions | null = + null; + let pubSub2: GlideClusterClientConfiguration.PubSubSubscriptions | null = + null; + let client1: TGlideClient | null = null; + let client2: TGlideClient | null = null; + + try { + const regularChannel = "regular_channel"; + const shardChannel = "shard_channel"; + + pubSub1 = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([regularChannel]), + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set([shardChannel]), + }, + {}, + ); + pubSub2 = createPubSubSubscription( + clusterMode, + { + [GlideClusterClientConfiguration.PubSubChannelModes + .Exact]: new Set([regularChannel]), + [GlideClusterClientConfiguration.PubSubChannelModes + .Sharded]: new Set([shardChannel]), + }, + {}, + ); + + [client1, client2] = await createClients( + clusterMode, + getOptions(clusterMode), + getOptions(clusterMode), + pubSub1, + pubSub2, + ); + + const minVersion = "7.0.0"; + + if (cmeCluster.checkIfServerVersionLessThan(minVersion)) { + return; // Skip test if server version is less than required + } + + // Test pubsubNumsub + const regularSubscribers = await client2.pubsubNumSub([ + regularChannel, + shardChannel, + ]); + expect(regularSubscribers).toEqual({ + [regularChannel]: 2, + [shardChannel]: 0, + }); + + // Test pubsubShardnumsub + const shardSubscribers = await ( + client2 as GlideClusterClient + ).pubsubShardNumSub([regularChannel, shardChannel]); + expect(shardSubscribers).toEqual({ + [regularChannel]: 0, + [shardChannel]: 2, + }); + } finally { + if (client1) { + await clientCleanup(client1, pubSub1 ? pubSub1 : undefined); + } + + if (client2) { + await clientCleanup(client2, pubSub2 ? pubSub2 : undefined); + } + } + }, + TIMEOUT, + ); +}); diff --git a/node/tests/RedisClient.test.ts b/node/tests/RedisClient.test.ts deleted file mode 100644 index debe11285d..0000000000 --- a/node/tests/RedisClient.test.ts +++ /dev/null @@ -1,319 +0,0 @@ -/** - * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 - */ - -import { - afterAll, - afterEach, - beforeAll, - describe, - expect, - it, -} from "@jest/globals"; -import { BufferReader, BufferWriter } from "protobufjs"; -import { v4 as uuidv4 } from "uuid"; -import { GlideClient, ProtocolVersion, Transaction } from ".."; -import { RedisCluster } from "../../utils/TestUtils.js"; -import { command_request } from "../src/ProtobufMessage"; -import { runBaseTests } from "./SharedTests"; -import { - checkSimple, - convertStringArrayToBuffer, - flushAndCloseClient, - getClientConfigurationOption, - intoString, - parseCommandLineArgs, - parseEndpoints, - transactionTest, -} from "./TestUtilities"; - -/* eslint-disable @typescript-eslint/no-var-requires */ - -type Context = { - client: GlideClient; -}; - -const TIMEOUT = 50000; - -describe("GlideClient", () => { - let testsFailed = 0; - let cluster: RedisCluster; - let client: GlideClient; - beforeAll(async () => { - const standaloneAddresses = - parseCommandLineArgs()["standalone-endpoints"]; - // Connect to cluster or create a new one based on the parsed addresses - cluster = standaloneAddresses - ? RedisCluster.initFromExistingCluster( - parseEndpoints(standaloneAddresses), - ) - : await RedisCluster.createCluster(false, 1, 1); - }, 20000); - - afterEach(async () => { - await flushAndCloseClient(false, cluster.getAddresses(), client); - }); - - afterAll(async () => { - if (testsFailed === 0) { - await cluster.close(); - } - }, TIMEOUT); - - it("test protobuf encode/decode delimited", () => { - // This test is required in order to verify that the autogenerated protobuf - // files has been corrected and the encoding/decoding works as expected. - // See "Manually compile protobuf files" in node/README.md to get more info about the fix. - const writer = new BufferWriter(); - const request = { - callbackIdx: 1, - singleCommand: { - requestType: 2, - argsArray: command_request.Command.ArgsArray.create({ - args: convertStringArrayToBuffer(["bar1", "bar2"]), - }), - }, - }; - const request2 = { - callbackIdx: 3, - singleCommand: { - requestType: 4, - argsArray: command_request.Command.ArgsArray.create({ - args: convertStringArrayToBuffer(["bar3", "bar4"]), - }), - }, - }; - command_request.CommandRequest.encodeDelimited(request, writer); - command_request.CommandRequest.encodeDelimited(request2, writer); - const buffer = writer.finish(); - const reader = new BufferReader(buffer); - - const dec_msg1 = command_request.CommandRequest.decodeDelimited(reader); - expect(dec_msg1.callbackIdx).toEqual(1); - expect(dec_msg1.singleCommand?.requestType).toEqual(2); - expect(dec_msg1.singleCommand?.argsArray?.args).toEqual( - convertStringArrayToBuffer(["bar1", "bar2"]), - ); - - const dec_msg2 = command_request.CommandRequest.decodeDelimited(reader); - expect(dec_msg2.callbackIdx).toEqual(3); - expect(dec_msg2.singleCommand?.requestType).toEqual(4); - expect(dec_msg2.singleCommand?.argsArray?.args).toEqual( - convertStringArrayToBuffer(["bar3", "bar4"]), - ); - }); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "info without parameters", - async (protocol) => { - client = await GlideClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - const result = await client.info(); - expect(intoString(result)).toEqual( - expect.stringContaining("# Server"), - ); - expect(intoString(result)).toEqual( - expect.stringContaining("# Replication"), - ); - expect(intoString(result)).toEqual( - expect.not.stringContaining("# Latencystats"), - ); - }, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "simple select test", - async (protocol) => { - client = await GlideClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - let selectResult = await client.select(0); - checkSimple(selectResult).toEqual("OK"); - - const key = uuidv4(); - const value = uuidv4(); - const result = await client.set(key, value); - checkSimple(result).toEqual("OK"); - - selectResult = await client.select(1); - checkSimple(selectResult).toEqual("OK"); - expect(await client.get(key)).toEqual(null); - - selectResult = await client.select(0); - checkSimple(selectResult).toEqual("OK"); - checkSimple(await client.get(key)).toEqual(value); - }, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `can send transactions_%p`, - async (protocol) => { - client = await GlideClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - const transaction = new Transaction(); - const expectedRes = await transactionTest(transaction); - transaction.select(0); - const result = await client.exec(transaction); - expectedRes.push("OK"); - expect(intoString(result)).toEqual(intoString(expectedRes)); - }, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "can return null on WATCH transaction failures", - async (protocol) => { - const client1 = await GlideClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - const client2 = await GlideClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - const transaction = new Transaction(); - transaction.get("key"); - const result1 = await client1.customCommand(["WATCH", "key"]); - expect(result1).toEqual("OK"); - - const result2 = await client2.set("key", "foo"); - expect(result2).toEqual("OK"); - - const result3 = await client1.exec(transaction); - expect(result3).toBeNull(); - - client1.close(); - client2.close(); - }, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "object freq transaction test_%p", - async (protocol) => { - const client = await GlideClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - - const key = uuidv4(); - const maxmemoryPolicyKey = "maxmemory-policy"; - const config = await client.configGet([maxmemoryPolicyKey]); - const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); - - try { - const transaction = new Transaction(); - transaction.configSet({ - [maxmemoryPolicyKey]: "allkeys-lfu", - }); - transaction.set(key, "foo"); - transaction.objectFreq(key); - - const response = await client.exec(transaction); - expect(response).not.toBeNull(); - - if (response != null) { - expect(response.length).toEqual(3); - expect(response[0]).toEqual("OK"); - expect(response[1]).toEqual("OK"); - expect(response[2]).toBeGreaterThanOrEqual(0); - } - } finally { - expect( - await client.configSet({ - [maxmemoryPolicyKey]: maxmemoryPolicy, - }), - ).toEqual("OK"); - } - - client.close(); - }, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "object idletime transaction test_%p", - async (protocol) => { - const client = await GlideClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - - const key = uuidv4(); - const maxmemoryPolicyKey = "maxmemory-policy"; - const config = await client.configGet([maxmemoryPolicyKey]); - const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); - - try { - const transaction = new Transaction(); - transaction.configSet({ - // OBJECT IDLETIME requires a non-LFU maxmemory-policy - [maxmemoryPolicyKey]: "allkeys-random", - }); - transaction.set(key, "foo"); - transaction.objectIdletime(key); - - const response = await client.exec(transaction); - expect(response).not.toBeNull(); - - if (response != null) { - expect(response.length).toEqual(3); - // transaction.configSet({[maxmemoryPolicyKey]: "allkeys-random"}); - expect(response[0]).toEqual("OK"); - // transaction.set(key, "foo"); - expect(response[1]).toEqual("OK"); - // transaction.objectIdletime(key); - expect(response[2]).toBeGreaterThanOrEqual(0); - } - } finally { - expect( - await client.configSet({ - [maxmemoryPolicyKey]: maxmemoryPolicy, - }), - ).toEqual("OK"); - } - - client.close(); - }, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "object refcount transaction test_%p", - async (protocol) => { - const client = await GlideClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - - const key = uuidv4(); - const transaction = new Transaction(); - transaction.set(key, "foo"); - transaction.objectRefcount(key); - - const response = await client.exec(transaction); - expect(response).not.toBeNull(); - - if (response != null) { - expect(response.length).toEqual(2); - expect(response[0]).toEqual("OK"); // transaction.set(key, "foo"); - expect(response[1]).toBeGreaterThanOrEqual(1); // transaction.objectRefcount(key); - } - - client.close(); - }, - ); - - runBaseTests({ - init: async (protocol, clientName?) => { - const options = getClientConfigurationOption( - cluster.getAddresses(), - protocol, - ); - options.protocol = protocol; - options.clientName = clientName; - testsFailed += 1; - client = await GlideClient.createClient(options); - return { client, context: { client } }; - }, - close: (context: Context, testSucceeded: boolean) => { - if (testSucceeded) { - testsFailed -= 1; - } - }, - timeout: TIMEOUT, - }); -}); diff --git a/node/tests/RedisClusterClient.test.ts b/node/tests/RedisClusterClient.test.ts deleted file mode 100644 index a7886fb3dc..0000000000 --- a/node/tests/RedisClusterClient.test.ts +++ /dev/null @@ -1,458 +0,0 @@ -/** - * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 - */ - -import { - afterAll, - afterEach, - beforeAll, - describe, - expect, - it, -} from "@jest/globals"; -import { v4 as uuidv4 } from "uuid"; - -import { - ClusterTransaction, - GlideClusterClient, - InfoOptions, - ProtocolVersion, -} from ".."; -import { RedisCluster } from "../../utils/TestUtils.js"; -import { checkIfServerVersionLessThan, runBaseTests } from "./SharedTests"; -import { - flushAndCloseClient, - getClientConfigurationOption, - getFirstResult, - intoArray, - intoString, - parseCommandLineArgs, - parseEndpoints, - transactionTest, -} from "./TestUtilities"; -type Context = { - client: GlideClusterClient; -}; - -const TIMEOUT = 50000; - -describe("GlideClusterClient", () => { - let testsFailed = 0; - let cluster: RedisCluster; - let client: GlideClusterClient; - beforeAll(async () => { - const clusterAddresses = parseCommandLineArgs()["cluster-endpoints"]; - // Connect to cluster or create a new one based on the parsed addresses - cluster = clusterAddresses - ? RedisCluster.initFromExistingCluster( - parseEndpoints(clusterAddresses), - ) - : await RedisCluster.createCluster(true, 3, 0); - }, 20000); - - afterEach(async () => { - await flushAndCloseClient(true, cluster.getAddresses(), client); - }); - - afterAll(async () => { - if (testsFailed === 0) { - await cluster.close(); - } - }); - - runBaseTests({ - init: async (protocol, clientName?) => { - const options = getClientConfigurationOption( - cluster.getAddresses(), - protocol, - ); - options.protocol = protocol; - options.clientName = clientName; - testsFailed += 1; - client = await GlideClusterClient.createClient(options); - return { - context: { - client, - }, - client, - }; - }, - close: (context: Context, testSucceeded: boolean) => { - if (testSucceeded) { - testsFailed -= 1; - } - }, - timeout: TIMEOUT, - }); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `info with server and replication_%p`, - async (protocol) => { - client = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - const info_server = getFirstResult( - await client.info([InfoOptions.Server]), - ); - expect(intoString(info_server)).toEqual( - expect.stringContaining("# Server"), - ); - - const infoReplicationValues = Object.values( - await client.info([InfoOptions.Replication]), - ); - - const replicationInfo = intoArray(infoReplicationValues); - - for (const item of replicationInfo) { - expect(item).toContain("role:master"); - expect(item).toContain("# Replication"); - } - }, - TIMEOUT, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `info with server and randomNode route_%p`, - async (protocol) => { - client = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - const result = await client.info( - [InfoOptions.Server], - "randomNode", - ); - expect(intoString(result)).toEqual( - expect.stringContaining("# Server"), - ); - expect(intoString(result)).toEqual( - expect.not.stringContaining("# Errorstats"), - ); - }, - TIMEOUT, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `route by address reaches correct node_%p`, - async (protocol) => { - // returns the line that contains the word "myself", up to that point. This is done because the values after it might change with time. - const cleanResult = (value: string) => { - return ( - value - .split("\n") - .find((line: string) => line.includes("myself")) - ?.split("myself")[0] ?? "" - ); - }; - - client = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - const result = cleanResult( - intoString( - await client.customCommand( - ["cluster", "nodes"], - "randomNode", - ), - ), - ); - - // check that routing without explicit port works - const host = result.split(" ")[1].split("@")[0] ?? ""; - - if (!host) { - throw new Error("No host could be parsed"); - } - - const secondResult = cleanResult( - intoString( - await client.customCommand(["cluster", "nodes"], { - type: "routeByAddress", - host, - }), - ), - ); - - expect(result).toEqual(secondResult); - - const [host2, port] = host.split(":"); - - // check that routing with explicit port works - const thirdResult = cleanResult( - intoString( - await client.customCommand(["cluster", "nodes"], { - type: "routeByAddress", - host: host2, - port: Number(port), - }), - ), - ); - - expect(result).toEqual(thirdResult); - }, - TIMEOUT, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `fail routing by address if no port is provided_%p`, - async (protocol) => { - client = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - expect(() => - client.info(undefined, { - type: "routeByAddress", - host: "foo", - }), - ).toThrowError(); - }, - TIMEOUT, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `config get and config set transactions test_%p`, - async (protocol) => { - client = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - const transaction = new ClusterTransaction(); - transaction.configSet({ timeout: "1000" }); - transaction.configGet(["timeout"]); - const result = await client.exec(transaction); - expect(intoString(result)).toEqual( - intoString(["OK", { timeout: "1000" }]), - ); - }, - TIMEOUT, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `can send transactions_%p`, - async (protocol) => { - client = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - const transaction = new ClusterTransaction(); - const expectedRes = await transactionTest(transaction); - const result = await client.exec(transaction); - expect(intoString(result)).toEqual(intoString(expectedRes)); - }, - TIMEOUT, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `can return null on WATCH transaction failures_%p`, - async (protocol) => { - const client1 = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - const client2 = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - const transaction = new ClusterTransaction(); - transaction.get("key"); - const result1 = await client1.customCommand(["WATCH", "key"]); - expect(result1).toEqual("OK"); - - const result2 = await client2.set("key", "foo"); - expect(result2).toEqual("OK"); - - const result3 = await client1.exec(transaction); - expect(result3).toBeNull(); - - client1.close(); - client2.close(); - }, - TIMEOUT, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `echo with all nodes routing_%p`, - async (protocol) => { - client = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - const message = uuidv4(); - const echoDict = await client.echo(message, "allNodes"); - - expect(typeof echoDict).toBe("object"); - expect(intoArray(echoDict)).toEqual( - expect.arrayContaining(intoArray([message])), - ); - }, - TIMEOUT, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `check that multi key command returns a cross slot error`, - async (protocol) => { - const client = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - - const versionLessThan7 = - await checkIfServerVersionLessThan("7.0.0"); - - const promises: Promise[] = [ - client.blpop(["abc", "zxy", "lkn"], 0.1), - client.rename("abc", "zxy"), - client.brpop(["abc", "zxy", "lkn"], 0.1), - client.smove("abc", "zxy", "value"), - client.renamenx("abc", "zxy"), - client.sinter(["abc", "zxy", "lkn"]), - client.sinterstore("abc", ["zxy", "lkn"]), - client.zinterstore("abc", ["zxy", "lkn"]), - client.sunionstore("abc", ["zxy", "lkn"]), - client.sunion(["abc", "zxy", "lkn"]), - client.pfcount(["abc", "zxy", "lkn"]), - client.sdiff(["abc", "zxy", "lkn"]), - client.sdiffstore("abc", ["zxy", "lkn"]), - // TODO all rest multi-key commands except ones tested below - ]; - - if (!versionLessThan7) { - promises.push(client.zintercard(["abc", "zxy", "lkn"])); - } - - for (const promise of promises) { - try { - await promise; - } catch (e) { - expect((e as Error).message.toLowerCase()).toContain( - "crossslot", - ); - } - } - - client.close(); - }, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `check that multi key command routed to multiple nodes`, - async (protocol) => { - const client = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - - await client.exists(["abc", "zxy", "lkn"]); - await client.unlink(["abc", "zxy", "lkn"]); - await client.del(["abc", "zxy", "lkn"]); - await client.mget(["abc", "zxy", "lkn"]); - await client.mset({ abc: "1", zxy: "2", lkn: "3" }); - // TODO touch - client.close(); - }, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "object freq transaction test_%p", - async (protocol) => { - const client = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - - const key = uuidv4(); - const maxmemoryPolicyKey = "maxmemory-policy"; - const config = await client.configGet([maxmemoryPolicyKey]); - const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); - - try { - const transaction = new ClusterTransaction(); - transaction.configSet({ - [maxmemoryPolicyKey]: "allkeys-lfu", - }); - transaction.set(key, "foo"); - transaction.objectFreq(key); - - const response = await client.exec(transaction); - expect(response).not.toBeNull(); - - if (response != null) { - expect(response.length).toEqual(3); - expect(response[0]).toEqual("OK"); - expect(response[1]).toEqual("OK"); - expect(response[2]).toBeGreaterThanOrEqual(0); - } - } finally { - expect( - await client.configSet({ - [maxmemoryPolicyKey]: maxmemoryPolicy, - }), - ).toEqual("OK"); - } - - client.close(); - }, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "object idletime transaction test_%p", - async (protocol) => { - const client = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - - const key = uuidv4(); - const maxmemoryPolicyKey = "maxmemory-policy"; - const config = await client.configGet([maxmemoryPolicyKey]); - const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); - - try { - const transaction = new ClusterTransaction(); - transaction.configSet({ - // OBJECT IDLETIME requires a non-LFU maxmemory-policy - [maxmemoryPolicyKey]: "allkeys-random", - }); - transaction.set(key, "foo"); - transaction.objectIdletime(key); - - const response = await client.exec(transaction); - expect(response).not.toBeNull(); - - if (response != null) { - expect(response.length).toEqual(3); - // transaction.configSet({[maxmemoryPolicyKey]: "allkeys-random"}); - expect(response[0]).toEqual("OK"); - // transaction.set(key, "foo"); - expect(response[1]).toEqual("OK"); - // transaction.objectIdletime(key); - expect(response[2]).toBeGreaterThanOrEqual(0); - } - } finally { - expect( - await client.configSet({ - [maxmemoryPolicyKey]: maxmemoryPolicy, - }), - ).toEqual("OK"); - } - - client.close(); - }, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "object refcount transaction test_%p", - async (protocol) => { - const client = await GlideClusterClient.createClient( - getClientConfigurationOption(cluster.getAddresses(), protocol), - ); - - const key = uuidv4(); - const transaction = new ClusterTransaction(); - transaction.set(key, "foo"); - transaction.objectRefcount(key); - - const response = await client.exec(transaction); - expect(response).not.toBeNull(); - - if (response != null) { - expect(response.length).toEqual(2); - expect(response[0]).toEqual("OK"); // transaction.set(key, "foo"); - expect(response[1]).toBeGreaterThanOrEqual(1); // transaction.objectRefcount(key); - } - - client.close(); - }, - ); -}); diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 8cee1409e8..25a44b5f92 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -2,75 +2,76 @@ * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +// This file contains tests common for standalone and cluster clients, it covers API defined in +// BaseClient.ts - commands which manipulate with keys. +// Each test cases has access to a client instance and, optionally, to a cluster - object, which +// represents a running server instance. See first 2 test cases as examples. + import { expect, it } from "@jest/globals"; -import { exec } from "child_process"; +import { HashDataType } from "src/BaseClient"; import { v4 as uuidv4 } from "uuid"; import { + BaseClientConfiguration, + BitFieldGet, + BitFieldIncrBy, + BitFieldOverflow, + BitFieldSet, + BitOffset, + BitOffsetMultiplier, + BitOverflowControl, + BitmapIndexType, + BitwiseOperation, ClosingError, + ClusterTransaction, + ConditionalChange, + Decoder, ExpireOptions, + FlushMode, + GeoUnit, + GeospatialData, GlideClient, GlideClusterClient, + GlideString, + InfBoundary, InfoOptions, InsertPosition, + ListDirection, ProtocolVersion, RequestError, + ReturnType, + ScoreFilter, Script, + SignedEncoding, + SortOrder, + TimeUnit, + Transaction, + UnsignedEncoding, + UpdateByScore, parseInfoResponse, } from "../"; +import { RedisCluster } from "../../utils/TestUtils"; +import { SingleNodeRoute } from "../build-ts/src/GlideClusterClient"; import { Client, GetAndSetRandomValue, - checkSimple, compareMaps, getFirstResult, - intoArray, - intoString, } from "./TestUtilities"; -async function getVersion(): Promise<[number, number, number]> { - const versionString = await new Promise((resolve, reject) => { - exec(`redis-server -v`, (error, stdout) => { - if (error) { - reject(error); - } else { - resolve(stdout); - } - }); - }); - const version = versionString.split("v=")[1].split(" ")[0]; - const numbers = version?.split("."); - - if (numbers.length != 3) { - return [0, 0, 0]; - } - - return [parseInt(numbers[0]), parseInt(numbers[1]), parseInt(numbers[2])]; -} - -export async function checkIfServerVersionLessThan( - minVersion: string, -): Promise { - const version = await getVersion(); - const versionToCompare = - version[0].toString() + - "." + - version[1].toString() + - "." + - version[2].toString(); - return versionToCompare < minVersion; -} - export type BaseClient = GlideClient | GlideClusterClient; -export function runBaseTests(config: { +// Same as `BaseClientConfiguration`, but all fields are optional +export type ClientConfig = Partial; + +export function runBaseTests(config: { init: ( protocol: ProtocolVersion, - clientName?: string, + configOverrides?: ClientConfig, ) => Promise<{ - context: Context; client: BaseClient; + cluster: RedisCluster; }>; - close: (context: Context, testSucceeded: boolean) => void; + close: (testSucceeded: boolean) => void; timeout?: number; }) { runCommonTests({ @@ -80,32 +81,35 @@ export function runBaseTests(config: { }); const runTest = async ( - test: (client: BaseClient) => Promise, + test: (client: BaseClient, cluster: RedisCluster) => Promise, protocol: ProtocolVersion, - clientName?: string, + configOverrides?: ClientConfig, ) => { - const { context, client } = await config.init(protocol, clientName); + const { client, cluster } = await config.init( + protocol, + configOverrides, + ); let testSucceeded = false; try { - await test(client); + await test(client, cluster); testSucceeded = true; } finally { - config.close(context, testSucceeded); + config.close(testSucceeded); } }; it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `should register client library name and version_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { - if (await checkIfServerVersionLessThan("7.2.0")) { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("7.2.0")) { return; } const result = await client.customCommand(["CLIENT", "INFO"]); - expect(intoString(result)).toContain("lib-name=GlideJS"); - expect(intoString(result)).toContain("lib-ver=unknown"); + expect(result).toContain("lib-name=GlideJS"); + expect(result).toContain("lib-ver=unknown"); }, protocol); }, config.timeout, @@ -160,12 +164,22 @@ export function runBaseTests(config: { async (protocol) => { await runTest( async (client: BaseClient) => { - expect(intoString(await client.clientGetName())).toBe( - "TEST_CLIENT", - ); + expect(await client.clientGetName()).toEqual("TEST_CLIENT"); + + if (client instanceof GlideClient) { + expect( + await client.clientGetName(Decoder.Bytes), + ).toEqual(Buffer.from("TEST_CLIENT")); + } else { + expect( + await client.clientGetName({ + decoder: Decoder.Bytes, + }), + ).toEqual(Buffer.from("TEST_CLIENT")); + } }, protocol, - "TEST_CLIENT", + { clientName: "TEST_CLIENT" }, ); }, config.timeout, @@ -183,9 +197,9 @@ export function runBaseTests(config: { key, value, ]); - checkSimple(setResult).toEqual("OK"); + expect(setResult).toEqual("OK"); const result = await client.customCommand(["GET", key]); - checkSimple(result).toEqual(value); + expect(result).toEqual(value); }, protocol); }, config.timeout, @@ -206,20 +220,20 @@ export function runBaseTests(config: { key1, value1, ]); - checkSimple(setResult1).toEqual("OK"); + expect(setResult1).toEqual("OK"); const setResult2 = await client.customCommand([ "SET", key2, value2, ]); - checkSimple(setResult2).toEqual("OK"); + expect(setResult2).toEqual("OK"); const mget_result = await client.customCommand([ "MGET", key1, key2, key3, ]); - checkSimple(mget_result).toEqual([value1, value2, null]); + expect(mget_result).toEqual([value1, value2, null]); }, protocol); }, config.timeout, @@ -239,7 +253,11 @@ export function runBaseTests(config: { expect(result).toEqual("OK"); result = await client.set(key3, value); expect(result).toEqual("OK"); - let deletedKeysNum = await client.del([key1, key2, key3]); + let deletedKeysNum = await client.del([ + key1, + Buffer.from(key2), + key3, + ]); expect(deletedKeysNum).toEqual(3); deletedKeysNum = await client.del([uuidv4()]); expect(deletedKeysNum).toEqual(0); @@ -262,15 +280,13 @@ export function runBaseTests(config: { `test config rewrite_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const serverInfo = intoString( - await client.info([InfoOptions.Server]), - ); + const serverInfo = await client.info([InfoOptions.Server]); const conf_file = parseInfoResponse( getFirstResult(serverInfo).toString(), )["config_file"]; if (conf_file.length > 0) { - checkSimple(await client.configRewrite()).toEqual("OK"); + expect(await client.configRewrite()).toEqual("OK"); } else { try { /// We expect Redis to return an error since the test cluster doesn't use redis.conf file @@ -293,22 +309,59 @@ export function runBaseTests(config: { /// we execute set and info so the commandstats will show `cmdstat_set::calls` greater than 1 /// after the configResetStat call we initiate an info command and the the commandstats won't contain `cmdstat_set`. await client.set("foo", "bar"); - const oldResult = await client.info([InfoOptions.Commandstats]); - const oldResultAsString = intoString(oldResult); - console.log(oldResult); - console.log(oldResultAsString); - expect(oldResultAsString).toContain("cmdstat_set"); - checkSimple(await client.configResetStat()).toEqual("OK"); - - const result = intoArray( - await client.info([InfoOptions.Commandstats]), - ); + const oldResult = + client instanceof GlideClient + ? await client.info([InfoOptions.Commandstats]) + : Object.values( + await client.info({ + sections: [InfoOptions.Commandstats], + }), + ).join(); + expect(oldResult).toContain("cmdstat_set"); + expect(await client.configResetStat()).toEqual("OK"); + + const result = + client instanceof GlideClient + ? await client.info([InfoOptions.Commandstats]) + : Object.values( + await client.info({ + sections: [InfoOptions.Commandstats], + }), + ).join(); expect(result).not.toContain("cmdstat_set"); }, protocol); }, config.timeout, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "lastsave %p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const today = new Date(); + today.setDate(today.getDate() - 1); + const yesterday = today.getTime() / 1000; // as epoch time + + expect(await client.lastsave()).toBeGreaterThan(yesterday); + + if (client instanceof GlideClusterClient) { + Object.values( + await client.lastsave({ route: "allNodes" }), + ).forEach((v) => expect(v).toBeGreaterThan(yesterday)); + } + + const response = + client instanceof GlideClient + ? await client.exec(new Transaction().lastsave()) + : await client.exec( + new ClusterTransaction().lastsave(), + ); + expect(response?.[0]).toBeGreaterThan(yesterday); + }, protocol); + }, + config.timeout, + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `testing mset and mget with multiple existing keys and one non existing key_%p`, async (protocol) => { @@ -322,10 +375,60 @@ export function runBaseTests(config: { [key2]: value, [key3]: value, }; - checkSimple(await client.mset(keyValueList)).toEqual("OK"); - checkSimple( + const valueEncoded = Buffer.from(value); + + expect(await client.mset(keyValueList)).toEqual("OK"); + expect( await client.mget([key1, key2, "nonExistingKey", key3]), ).toEqual([value, value, null, value]); + + //mget with binary buffers + expect(await client.mset(keyValueList)).toEqual("OK"); + expect( + await client.mget( + [key1, key2, "nonExistingKey", key3], + Decoder.Bytes, + ), + ).toEqual([valueEncoded, valueEncoded, null, valueEncoded]); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `msetnx test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const key3 = "{key}-3" + uuidv4(); + const nonExistingKey = uuidv4(); + const value = uuidv4(); + const keyValueMap1 = { + [key1]: value, + [key2]: value, + }; + const keyValueMap2 = { + [key2]: value, + [key3]: value, + }; + + expect(await client.msetnx(keyValueMap1)).toEqual(true); + + expect(await client.mget([key1, key2, nonExistingKey])).toEqual( + [value, value, null], + ); + + expect(await client.msetnx(keyValueMap2)).toEqual(false); + + expect(await client.get(key3)).toEqual(null); + expect(await client.get(key2)).toEqual(value); + + // empty map and RequestError is thrown + const emptyMap = {}; + await expect(client.msetnx(emptyMap)).rejects.toThrow( + RequestError, + ); }, protocol); }, config.timeout, @@ -336,13 +439,17 @@ export function runBaseTests(config: { async (protocol) => { await runTest(async (client: BaseClient) => { const key = uuidv4(); - checkSimple(await client.set(key, "10")).toEqual("OK"); + const keyEncoded = Buffer.from(key); + expect(await client.set(key, "10")).toEqual("OK"); expect(await client.incr(key)).toEqual(11); - checkSimple(await client.get(key)).toEqual("11"); - checkSimple(await client.incrBy(key, 4)).toEqual(15); - checkSimple(await client.get(key)).toEqual("15"); - checkSimple(await client.incrByFloat(key, 1.5)).toEqual(16.5); - checkSimple(await client.get(key)).toEqual("16.5"); + expect(await client.incr(keyEncoded)).toEqual(12); + expect(await client.get(key)).toEqual("12"); + expect(await client.incrBy(key, 4)).toEqual(16); + expect(await client.incrBy(keyEncoded, 1)).toEqual(17); + expect(await client.get(key)).toEqual("17"); + expect(await client.incrByFloat(key, 1.5)).toEqual(18.5); + expect(await client.incrByFloat(key, 1.5)).toEqual(20); + expect(await client.get(key)).toEqual("20"); }, protocol); }, config.timeout, @@ -357,11 +464,11 @@ export function runBaseTests(config: { const key3 = uuidv4(); /// key1 and key2 does not exist, so it set to 0 before performing the operation. expect(await client.incr(key1)).toEqual(1); - checkSimple(await client.get(key1)).toEqual("1"); + expect(await client.get(key1)).toEqual("1"); expect(await client.incrBy(key2, 2)).toEqual(2); - checkSimple(await client.get(key2)).toEqual("2"); + expect(await client.get(key2)).toEqual("2"); expect(await client.incrByFloat(key3, -0.5)).toEqual(-0.5); - checkSimple(await client.get(key3)).toEqual("-0.5"); + expect(await client.get(key3)).toEqual("-0.5"); }, protocol); }, config.timeout, @@ -372,7 +479,7 @@ export function runBaseTests(config: { async (protocol) => { await runTest(async (client: BaseClient) => { const key = uuidv4(); - checkSimple(await client.set(key, "foo")).toEqual("OK"); + expect(await client.set(key, "foo")).toEqual("OK"); try { expect(await client.incr(key)).toThrow(); @@ -406,8 +513,26 @@ export function runBaseTests(config: { `ping test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - checkSimple(await client.ping()).toEqual("PONG"); - checkSimple(await client.ping("Hello")).toEqual("Hello"); + const pongEncoded = Buffer.from("PONG"); + expect(await client.ping()).toEqual("PONG"); + expect(await client.ping({ message: "Hello" })).toEqual( + "Hello", + ); + expect( + await client.ping({ + message: pongEncoded, + decoder: Decoder.String, + }), + ).toEqual("PONG"); + expect(await client.ping({ decoder: Decoder.Bytes })).toEqual( + pongEncoded, + ); + expect( + await client.ping({ + message: "Hello", + decoder: Decoder.Bytes, + }), + ).toEqual(Buffer.from("Hello")); }, protocol); }, config.timeout, @@ -430,11 +555,14 @@ export function runBaseTests(config: { async (protocol) => { await runTest(async (client: BaseClient) => { const key = uuidv4(); - checkSimple(await client.set(key, "10")).toEqual("OK"); + const keyEncoded = Buffer.from(key); + expect(await client.set(key, "10")).toEqual("OK"); expect(await client.decr(key)).toEqual(9); - checkSimple(await client.get(key)).toEqual("9"); - expect(await client.decrBy(key, 4)).toEqual(5); - checkSimple(await client.get(key)).toEqual("5"); + expect(await client.decr(keyEncoded)).toEqual(8); + expect(await client.get(key)).toEqual("8"); + expect(await client.decrBy(key, 4)).toEqual(4); + expect(await client.decrBy(keyEncoded, 1)).toEqual(3); + expect(await client.get(key)).toEqual("3"); }, protocol); }, config.timeout, @@ -449,10 +577,10 @@ export function runBaseTests(config: { /// key1 and key2 does not exist, so it set to 0 before performing the operation. expect(await client.get(key1)).toBeNull(); expect(await client.decr(key1)).toEqual(-1); - checkSimple(await client.get(key1)).toEqual("-1"); + expect(await client.get(key1)).toEqual("-1"); expect(await client.get(key2)).toBeNull(); expect(await client.decrBy(key2, 3)).toEqual(-3); - checkSimple(await client.get(key2)).toEqual("-3"); + expect(await client.get(key2)).toEqual("-3"); }, protocol); }, config.timeout, @@ -486,125 +614,166 @@ export function runBaseTests(config: { ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `config get and config set with timeout parameter_%p`, + `bitop test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const prevTimeout = (await client.configGet([ - "timeout", - ])) as Record; - checkSimple( - await client.configSet({ timeout: "1000" }), - ).toEqual("OK"); - const currTimeout = (await client.configGet([ - "timeout", - ])) as Record; - checkSimple(currTimeout).toEqual({ timeout: "1000" }); - /// Revert to the pervious configuration - checkSimple( - await client.configSet({ - timeout: prevTimeout["timeout"], - }), - ).toEqual("OK"); - }, protocol); - }, - config.timeout, - ); + const key1 = `{key}-${uuidv4()}`; + const key2 = `{key}-${uuidv4()}`; + const key3 = `{key}-${uuidv4()}`; + const keys = [key1, key2]; + const keysEncoded = [Buffer.from(key1), Buffer.from(key2)]; + const destination = `{key}-${uuidv4()}`; + const nonExistingKey1 = `{key}-${uuidv4()}`; + const nonExistingKey2 = `{key}-${uuidv4()}`; + const nonExistingKey3 = `{key}-${uuidv4()}`; + const nonExistingKeys = [ + nonExistingKey1, + nonExistingKey2, + nonExistingKey3, + ]; + const setKey = `{key}-${uuidv4()}`; + const value1 = "foobar"; + const value2 = "abcdef"; - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `testing hset and hget with multiple existing fields and one non existing field_%p`, - async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const field1 = uuidv4(); - const field2 = uuidv4(); - const value = uuidv4(); - const fieldValueMap = { - [field1]: value, - [field2]: value, - }; - expect(await client.hset(key, fieldValueMap)).toEqual(2); - checkSimple(await client.hget(key, field1)).toEqual(value); - checkSimple(await client.hget(key, field2)).toEqual(value); - expect(await client.hget(key, "nonExistingField")).toEqual( - null, - ); - }, protocol); - }, - config.timeout, - ); + expect(await client.set(key1, value1)).toEqual("OK"); + expect(await client.set(key2, value2)).toEqual("OK"); + expect( + await client.bitop(BitwiseOperation.AND, destination, keys), + ).toEqual(6); + expect(await client.get(destination)).toEqual("`bc`ab"); + expect( + await client.bitop( + BitwiseOperation.OR, + destination, + keysEncoded, + ), + ).toEqual(6); + expect(await client.get(destination)).toEqual("goofev"); - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `hdel multiple existing fields, an non existing field and an non existing key_%p`, - async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const field1 = uuidv4(); - const field2 = uuidv4(); - const field3 = uuidv4(); - const value = uuidv4(); - const fieldValueMap = { - [field1]: value, - [field2]: value, - [field3]: value, - }; + // reset values for simplicity of results in XOR + expect(await client.set(key1, "a")).toEqual("OK"); + expect(await client.set(key2, "b")).toEqual("OK"); + expect( + await client.bitop(BitwiseOperation.XOR, destination, keys), + ).toEqual(1); + expect(await client.get(destination)).toEqual("\u0003"); - expect(await client.hset(key, fieldValueMap)).toEqual(3); - expect(await client.hdel(key, [field1, field2])).toEqual(2); - expect(await client.hdel(key, ["nonExistingField"])).toEqual(0); - expect(await client.hdel("nonExistingKey", [field3])).toEqual( - 0, - ); - }, protocol); - }, - config.timeout, - ); + // test single source key + expect( + await client.bitop(BitwiseOperation.AND, destination, [ + key1, + ]), + ).toEqual(1); + expect(await client.get(destination)).toEqual("a"); + expect( + await client.bitop(BitwiseOperation.OR, destination, [ + key1, + ]), + ).toEqual(1); + expect(await client.get(destination)).toEqual("a"); + expect( + await client.bitop(BitwiseOperation.XOR, destination, [ + key1, + ]), + ).toEqual(1); + expect(await client.get(destination)).toEqual("a"); - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `testing hmget with multiple existing fields, an non existing field and an non existing key_%p`, - async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const field1 = uuidv4(); - const field2 = uuidv4(); - const value = uuidv4(); - const fieldValueMap = { - [field1]: value, - [field2]: value, - }; - expect(await client.hset(key, fieldValueMap)).toEqual(2); - checkSimple( - await client.hmget(key, [ - field1, - "nonExistingField", - field2, + // Sets to a string (not a space character) with value 11000010 10011110. + expect(await client.set(key3, "ž")).toEqual("OK"); + expect(await client.getbit(key3, 0)).toEqual(1); + expect( + await client.bitop(BitwiseOperation.NOT, destination, [ + key3, ]), - ).toEqual([value, null, value]); + ).toEqual(2); + // Value becomes 00111101 01100001. + expect(await client.get(destination)).toEqual("=a"); + + expect(await client.setbit(key1, 0, 1)).toEqual(0); expect( - await client.hmget("nonExistingKey", [field1, field2]), - ).toEqual([null, null]); + await client.bitop(BitwiseOperation.NOT, destination, [ + key1, + ]), + ).toEqual(1); + expect(await client.get(destination)).toEqual("\u001e"); + + // stores null when all keys hold empty strings + expect( + await client.bitop( + BitwiseOperation.AND, + destination, + nonExistingKeys, + ), + ).toEqual(0); + expect(await client.get(destination)).toBeNull(); + expect( + await client.bitop( + BitwiseOperation.OR, + destination, + nonExistingKeys, + ), + ).toEqual(0); + expect(await client.get(destination)).toBeNull(); + expect( + await client.bitop( + BitwiseOperation.XOR, + destination, + nonExistingKeys, + ), + ).toEqual(0); + expect(await client.get(destination)).toBeNull(); + expect( + await client.bitop(BitwiseOperation.NOT, destination, [ + nonExistingKey1, + ]), + ).toEqual(0); + expect(await client.get(destination)).toBeNull(); + + // invalid argument - source key list cannot be empty + await expect( + client.bitop(BitwiseOperation.OR, destination, []), + ).rejects.toThrow(RequestError); + + // invalid arguments - NOT cannot be passed more than 1 key + await expect( + client.bitop(BitwiseOperation.NOT, destination, keys), + ).rejects.toThrow(RequestError); + + expect(await client.sadd(setKey, ["foo"])).toEqual(1); + // invalid argument - source key has the wrong type + await expect( + client.bitop(BitwiseOperation.AND, destination, [setKey]), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `hexists existing field, an non existing field and an non existing key_%p`, + `getbit test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const field1 = uuidv4(); - const field2 = uuidv4(); - const fieldValueMap = { - [field1]: "value1", - [field2]: "value2", - }; - expect(await client.hset(key, fieldValueMap)).toEqual(2); - expect(await client.hexists(key, field1)).toEqual(true); - expect(await client.hexists(key, "nonExistingField")).toEqual( - false, + const key = `{key}-${uuidv4()}`; + const nonExistingKey = `{key}-${uuidv4()}`; + const setKey = `{key}-${uuidv4()}`; + + expect(await client.set(key, "foo")).toEqual("OK"); + expect(await client.getbit(key, 1)).toEqual(1); + // When offset is beyond the string length, the string is assumed to be a contiguous space with 0 bits. + expect(await client.getbit(Buffer.from(key), 1000)).toEqual(0); + // When key does not exist it is assumed to be an empty string, so offset is always out of range and the + // value is also assumed to be a contiguous space with 0 bits. + expect(await client.getbit(nonExistingKey, 1)).toEqual(0); + + // invalid argument - offset can't be negative + await expect(client.getbit(key, -1)).rejects.toThrow( + RequestError, ); - expect(await client.hexists("nonExistingKey", field2)).toEqual( - false, + + // key exists, but it is not a string + expect(await client.sadd(setKey, ["foo"])).toEqual(1); + await expect(client.getbit(setKey, 0)).rejects.toThrow( + RequestError, ); }, protocol); }, @@ -612,46 +781,29 @@ export function runBaseTests(config: { ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `hgetall with multiple fields in an existing key and one non existing key_%p`, + `setbit test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const field1 = uuidv4(); - const field2 = uuidv4(); - const value = uuidv4(); - const fieldValueMap = { - [field1]: value, - [field2]: value, - }; - expect(await client.hset(key, fieldValueMap)).toEqual(2); + const key = `{key}-${uuidv4()}`; + const setKey = `{key}-${uuidv4()}`; - expect(intoString(await client.hgetall(key))).toEqual( - intoString({ - [field1]: value, - [field2]: value, - }), + expect(await client.setbit(key, 1, 1)).toEqual(0); + expect(await client.setbit(Buffer.from(key), 1, 0)).toEqual(1); + + // invalid argument - offset can't be negative + await expect(client.setbit(key, -1, 1)).rejects.toThrow( + RequestError, ); - expect(await client.hgetall("nonExistingKey")).toEqual({}); - }, protocol); - }, - config.timeout, - ); + // invalid argument - "value" arg must be 0 or 1 + await expect(client.setbit(key, 0, 2)).rejects.toThrow( + RequestError, + ); - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `hincrBy and hincrByFloat with existing key and field_%p`, - async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const field = uuidv4(); - const fieldValueMap = { - [field]: "10", - }; - expect(await client.hset(key, fieldValueMap)).toEqual(1); - expect(await client.hincrBy(key, field, 1)).toEqual(11); - expect(await client.hincrBy(key, field, 4)).toEqual(15); - expect(await client.hincrByFloat(key, field, 1.5)).toEqual( - 16.5, + // key exists, but it is not a string + expect(await client.sadd(setKey, ["foo"])).toEqual(1); + await expect(client.setbit(setKey, 0, 0)).rejects.toThrow( + RequestError, ); }, protocol); }, @@ -659,540 +811,1055 @@ export function runBaseTests(config: { ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `hincrBy and hincrByFloat with non existing key and non existing field_%p`, + `bitpos and bitposInterval test_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { - const key1 = uuidv4(); - const key2 = uuidv4(); - const field = uuidv4(); - const fieldValueMap = { - [field]: "10", - }; - expect( - await client.hincrBy("nonExistingKey", field, 1), - ).toEqual(1); - expect(await client.hset(key1, fieldValueMap)).toEqual(1); + await runTest(async (client: BaseClient, cluster) => { + const key = `{key}-${uuidv4()}`; + const nonExistingKey = `{key}-${uuidv4()}`; + const setKey = `{key}-${uuidv4()}`; + const value = "?f0obar"; // 00111111 01100110 00110000 01101111 01100010 01100001 01110010 + + expect(await client.set(key, value)).toEqual("OK"); + expect(await client.bitpos(key, 0)).toEqual(0); + expect(await client.bitpos(Buffer.from(key), 1)).toEqual(2); + expect(await client.bitpos(key, 1, 1)).toEqual(9); + expect(await client.bitposInterval(key, 0, 3, 5)).toEqual(24); expect( - await client.hincrBy(key1, "nonExistingField", 2), - ).toEqual(2); - expect(await client.hset(key2, fieldValueMap)).toEqual(1); + await client.bitposInterval(Buffer.from(key), 0, 3, 5), + ).toEqual(24); + + // -1 is returned if start > end + expect(await client.bitposInterval(key, 0, 1, 0)).toEqual(-1); + + // `BITPOS` returns -1 for non-existing strings + expect(await client.bitpos(nonExistingKey, 1)).toEqual(-1); expect( - await client.hincrByFloat(key2, "nonExistingField", -0.5), - ).toEqual(-0.5); + await client.bitposInterval(nonExistingKey, 1, 3, 5), + ).toEqual(-1); + + // invalid argument - bit value must be 0 or 1 + await expect(client.bitpos(key, 2)).rejects.toThrow( + RequestError, + ); + await expect( + client.bitposInterval(key, 2, 3, 5), + ).rejects.toThrow(RequestError); + + // key exists, but it is not a string + expect(await client.sadd(setKey, ["foo"])).toEqual(1); + await expect(client.bitpos(setKey, 1)).rejects.toThrow( + RequestError, + ); + await expect( + client.bitposInterval(setKey, 1, 1, -1), + ).rejects.toThrow(RequestError); + + if (cluster.checkIfServerVersionLessThan("7.0.0")) { + await expect( + client.bitposInterval( + key, + 1, + 1, + -1, + BitmapIndexType.BYTE, + ), + ).rejects.toThrow(RequestError); + await expect( + client.bitposInterval( + key, + 1, + 1, + -1, + BitmapIndexType.BIT, + ), + ).rejects.toThrow(RequestError); + } else { + expect( + await client.bitposInterval( + key, + 0, + 3, + 5, + BitmapIndexType.BYTE, + ), + ).toEqual(24); + expect( + await client.bitposInterval( + key, + 1, + 43, + -2, + BitmapIndexType.BIT, + ), + ).toEqual(47); + expect( + await client.bitposInterval( + nonExistingKey, + 1, + 3, + 5, + BitmapIndexType.BYTE, + ), + ).toEqual(-1); + expect( + await client.bitposInterval( + nonExistingKey, + 1, + 3, + 5, + BitmapIndexType.BIT, + ), + ).toEqual(-1); + + // -1 is returned if the bit value wasn't found + expect( + await client.bitposInterval( + key, + 1, + -1, + -1, + BitmapIndexType.BIT, + ), + ).toEqual(-1); + + // key exists, but it is not a string + await expect( + client.bitposInterval( + setKey, + 1, + 1, + -1, + BitmapIndexType.BIT, + ), + ).rejects.toThrow(RequestError); + } }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `hincrBy and hincrByFloat with a field that contains a value of string that can not be represented as as integer or float_%p`, + `bitfield test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const field = uuidv4(); - const fieldValueMap = { - [field]: "foo", - }; - expect(await client.hset(key, fieldValueMap)).toEqual(1); + const key1 = `{key}-${uuidv4()}`; + const key2 = `{key}-${uuidv4()}`; + const nonExistingKey = `{key}-${uuidv4()}`; + const setKey = `{key}-${uuidv4()}`; + const foobar = "foobar"; + const u2 = new UnsignedEncoding(2); + const u7 = new UnsignedEncoding(7); + const i3 = new SignedEncoding(3); + const i8 = new SignedEncoding(8); + const offset1 = new BitOffset(1); + const offset5 = new BitOffset(5); + const offset_multiplier4 = new BitOffsetMultiplier(4); + const offset_multiplier8 = new BitOffsetMultiplier(8); + const overflowSet = new BitFieldSet(u2, offset1, -10); + const overflowGet = new BitFieldGet(u2, offset1); + + // binary value: 01100110 01101111 01101111 01100010 01100001 01110010 + expect(await client.set(key1, foobar)).toEqual("OK"); + + // SET tests + expect( + await client.bitfield(key1, [ + // binary value becomes: 0(10)00110 01101111 01101111 01100010 01100001 01110010 + new BitFieldSet(u2, offset1, 2), + // binary value becomes: 01000(011) 01101111 01101111 01100010 01100001 01110010 + new BitFieldSet(i3, offset5, 3), + // binary value becomes: 01000011 01101111 01101111 0110(0010 010)00001 01110010 + new BitFieldSet(u7, offset_multiplier4, 18), + // addressing with SET or INCRBY bits outside the current string length will enlarge the string, + // zero-padding it, as needed, for the minimal length needed, according to the most far bit touched. + // + // binary value becomes: + // 01000011 01101111 01101111 01100010 01000001 01110010 00000000 00000000 (00010100) + new BitFieldSet(i8, offset_multiplier8, 20), + new BitFieldGet(u2, offset1), + new BitFieldGet(i3, offset5), + new BitFieldGet(u7, offset_multiplier4), + new BitFieldGet(i8, offset_multiplier8), + ]), + ).toEqual([3, -2, 19, 0, 2, 3, 18, 20]); - try { - expect(await client.hincrBy(key, field, 2)).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "hash value is not an integer", - ); - } + // INCRBY tests + expect( + await client.bitfield(Buffer.from(key1), [ + // binary value becomes: + // 0(11)00011 01101111 01101111 01100010 01000001 01110010 00000000 00000000 00010100 + new BitFieldIncrBy(u2, offset1, 1), + // binary value becomes: + // 01100(101) 01101111 01101111 01100010 01000001 01110010 00000000 00000000 00010100 + new BitFieldIncrBy(i3, offset5, 2), + // binary value becomes: + // 01100101 01101111 01101111 0110(0001 111)00001 01110010 00000000 00000000 00010100 + new BitFieldIncrBy(u7, offset_multiplier4, -3), + // binary value becomes: + // 01100101 01101111 01101111 01100001 11100001 01110010 00000000 00000000 (00011110) + new BitFieldIncrBy(i8, offset_multiplier8, 10), + ]), + ).toEqual([3, -3, 15, 30]); - try { - expect( - await client.hincrByFloat(key, field, 1.5), - ).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "hash value is not a float", - ); - } + // OVERFLOW WRAP is used by default if no OVERFLOW is specified + expect( + await client.bitfield(key2, [ + overflowSet, + new BitFieldOverflow(BitOverflowControl.WRAP), + overflowSet, + overflowGet, + ]), + ).toEqual([0, 2, 2]); + + // OVERFLOW affects only SET or INCRBY after OVERFLOW subcommand + expect( + await client.bitfield(key2, [ + overflowSet, + new BitFieldOverflow(BitOverflowControl.SAT), + overflowSet, + overflowGet, + new BitFieldOverflow(BitOverflowControl.FAIL), + overflowSet, + ]), + ).toEqual([2, 2, 3, null]); + + // if the key doesn't exist, the operation is performed as though the missing value was a string with all bits + // set to 0. + expect( + await client.bitfield(nonExistingKey, [ + new BitFieldSet( + new UnsignedEncoding(2), + new BitOffset(3), + 2, + ), + ]), + ).toEqual([0]); + + // empty subcommands argument returns an empty list + expect(await client.bitfield(key1, [])).toEqual([]); + + // invalid argument - offset must be >= 0 + await expect( + client.bitfield(key1, [ + new BitFieldSet( + new UnsignedEncoding(5), + new BitOffset(-1), + 1, + ), + ]), + ).rejects.toThrow(RequestError); + + // invalid argument - encoding size must be > 0 + await expect( + client.bitfield(key1, [ + new BitFieldSet( + new UnsignedEncoding(0), + new BitOffset(1), + 1, + ), + ]), + ).rejects.toThrow(RequestError); + + // invalid argument - unsigned encoding must be < 64 + await expect( + client.bitfield(key1, [ + new BitFieldSet( + new UnsignedEncoding(64), + new BitOffset(1), + 1, + ), + ]), + ).rejects.toThrow(RequestError); + + // invalid argument - signed encoding must be < 65 + await expect( + client.bitfield(key1, [ + new BitFieldSet( + new SignedEncoding(65), + new BitOffset(1), + 1, + ), + ]), + ).rejects.toThrow(RequestError); + + // key exists, but it is not a string + expect(await client.sadd(setKey, [foobar])).toEqual(1); + await expect( + client.bitfield(setKey, [ + new BitFieldSet( + new SignedEncoding(3), + new BitOffset(1), + 2, + ), + ]), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `hlen test_%p`, + `bitfieldReadOnly test_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { - const key1 = uuidv4(); - const field1 = uuidv4(); - const field2 = uuidv4(); - const fieldValueMap = { - [field1]: "value1", - [field2]: "value2", - }; + await runTest(async (client: BaseClient, cluster) => { + if (cluster.checkIfServerVersionLessThan("6.0.0")) { + return; + } - expect(await client.hset(key1, fieldValueMap)).toEqual(2); - expect(await client.hlen(key1)).toEqual(2); - expect(await client.hdel(key1, [field1])).toEqual(1); - expect(await client.hlen(key1)).toEqual(1); - expect(await client.hlen("nonExistingHash")).toEqual(0); + const key = `{key}-${uuidv4()}`; + const nonExistingKey = `{key}-${uuidv4()}`; + const setKey = `{key}-${uuidv4()}`; + const foobar = "foobar"; + const unsignedOffsetGet = new BitFieldGet( + new UnsignedEncoding(2), + new BitOffset(1), + ); + + // binary value: 01100110 01101111 01101111 01100010 01100001 01110010 + expect(await client.set(key, foobar)).toEqual("OK"); + expect( + await client.bitfieldReadOnly(key, [ + // Get value in: 0(11)00110 01101111 01101111 01100010 01100001 01110010 00010100 + unsignedOffsetGet, + // Get value in: 01100(110) 01101111 01101111 01100010 01100001 01110010 00010100 + new BitFieldGet( + new SignedEncoding(3), + new BitOffset(5), + ), + // Get value in: 01100110 01101111 01101(111 0110)0010 01100001 01110010 00010100 + new BitFieldGet( + new UnsignedEncoding(7), + new BitOffsetMultiplier(3), + ), + // Get value in: 01100110 01101111 (01101111) 01100010 01100001 01110010 00010100 + new BitFieldGet( + new SignedEncoding(8), + new BitOffsetMultiplier(2), + ), + ]), + ).toEqual([3, -2, 118, 111]); + + // offset is greater than current length of string: the operation is performed like the missing part all + // consists of bits set to 0. + expect( + await client.bitfieldReadOnly(Buffer.from(key), [ + new BitFieldGet( + new UnsignedEncoding(3), + new BitOffset(100), + ), + ]), + ).toEqual([0]); + + // similarly, if the key doesn't exist, the operation is performed as though the missing value was a string with + // all bits set to 0. + expect( + await client.bitfieldReadOnly(nonExistingKey, [ + unsignedOffsetGet, + ]), + ).toEqual([0]); + + // empty subcommands argument returns an empty list + expect(await client.bitfieldReadOnly(key, [])).toEqual([]); + + // invalid argument - offset must be >= 0 + await expect( + client.bitfieldReadOnly(key, [ + new BitFieldGet( + new UnsignedEncoding(5), + new BitOffset(-1), + ), + ]), + ).rejects.toThrow(RequestError); + + // invalid argument - encoding size must be > 0 + await expect( + client.bitfieldReadOnly(key, [ + new BitFieldGet( + new UnsignedEncoding(0), + new BitOffset(1), + ), + ]), + ).rejects.toThrow(RequestError); + + // invalid argument - unsigned encoding must be < 64 + await expect( + client.bitfieldReadOnly(key, [ + new BitFieldGet( + new UnsignedEncoding(64), + new BitOffset(1), + ), + ]), + ).rejects.toThrow(RequestError); + + // invalid argument - signed encoding must be < 65 + await expect( + client.bitfieldReadOnly(key, [ + new BitFieldGet( + new SignedEncoding(65), + new BitOffset(1), + ), + ]), + ).rejects.toThrow(RequestError); + + // key exists, but it is not a string + expect(await client.sadd(setKey, [foobar])).toEqual(1); + await expect( + client.bitfieldReadOnly(setKey, [ + new BitFieldGet( + new SignedEncoding(3), + new BitOffset(1), + ), + ]), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `hvals test_%p`, + `config get and config set with timeout parameter_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = uuidv4(); - const field1 = uuidv4(); - const field2 = uuidv4(); - const fieldValueMap = { - [field1]: "value1", - [field2]: "value2", - }; - - expect(await client.hset(key1, fieldValueMap)).toEqual(2); - checkSimple(await client.hvals(key1)).toEqual([ - "value1", - "value2", - ]); - expect(await client.hdel(key1, [field1])).toEqual(1); - checkSimple(await client.hvals(key1)).toEqual(["value2"]); - expect(await client.hvals("nonExistingHash")).toEqual([]); + const prevTimeout = (await client.configGet([ + "timeout", + ])) as Record; + expect(await client.configSet({ timeout: "1000" })).toEqual( + "OK", + ); + const currTimeout = (await client.configGet([ + "timeout", + ])) as Record; + expect(currTimeout).toEqual({ timeout: "1000" }); + /// Revert to the pervious configuration + expect( + await client.configSet({ + timeout: prevTimeout["timeout"], + }), + ).toEqual("OK"); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `hsetnx test_%p`, + `getdel test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { const key1 = uuidv4(); + const value1 = uuidv4(); + const value1Encoded = Buffer.from(value1); const key2 = uuidv4(); - const field = uuidv4(); - expect(await client.hsetnx(key1, field, "value")).toEqual(true); - expect(await client.hsetnx(key1, field, "newValue")).toEqual( - false, + expect(await client.set(key1, value1)).toEqual("OK"); + expect(await client.getdel(key1)).toEqual(value1); + expect(await client.getdel(key1)).toEqual(null); + + expect(await client.set(key1, value1)).toEqual("OK"); + expect(await client.getdel(key1, Decoder.Bytes)).toEqual( + value1Encoded, ); - checkSimple(await client.hget(key1, field)).toEqual("value"); + expect(await client.getdel(key1, Decoder.Bytes)).toEqual(null); - checkSimple(await client.set(key2, "value")).toEqual("OK"); - await expect( - client.hsetnx(key2, field, "value"), - ).rejects.toThrow(); + // key isn't a string + expect(await client.sadd(key2, ["a"])).toEqual(1); + await expect(client.getdel(key2)).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `lpush, lpop and lrange with existing and non existing key_%p`, + `getrange test_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { + await runTest(async (client: BaseClient, cluster) => { const key = uuidv4(); - const valueList = ["value4", "value3", "value2", "value1"]; - expect(await client.lpush(key, valueList)).toEqual(4); - checkSimple(await client.lpop(key)).toEqual("value1"); - checkSimple(await client.lrange(key, 0, -1)).toEqual([ - "value2", - "value3", - "value4", - ]); - checkSimple(await client.lpopCount(key, 2)).toEqual([ - "value2", - "value3", - ]); - expect(await client.lrange("nonExistingKey", 0, -1)).toEqual( - [], + const nonStringKey = uuidv4(); + const valueEncoded = Buffer.from("This is a string"); + + expect(await client.set(key, "This is a string")).toEqual("OK"); + expect(await client.getrange(key, 0, 3)).toEqual("This"); + expect(await client.getrange(key, -3, -1)).toEqual("ing"); + expect(await client.getrange(key, 0, -1)).toEqual( + "This is a string", ); - expect(await client.lpop("nonExistingKey")).toEqual(null); + + // range of binary buffer + expect(await client.set(key, "This is a string")).toEqual("OK"); + expect(await client.getrange(key, 0, 3, Decoder.Bytes)).toEqual( + valueEncoded.subarray(0, 4), + ); + expect( + await client.getrange(key, -3, -1, Decoder.Bytes), + ).toEqual(valueEncoded.subarray(-3, valueEncoded.length)); + expect( + await client.getrange(key, 0, -1, Decoder.Bytes), + ).toEqual(valueEncoded.subarray(0, valueEncoded.length)); + + // out of range + expect(await client.getrange(key, 10, 100)).toEqual("string"); + expect(await client.getrange(key, -200, -3)).toEqual( + "This is a stri", + ); + expect(await client.getrange(key, 100, 200)).toEqual(""); + + // incorrect range + expect(await client.getrange(key, -1, -3)).toEqual(""); + + // a bug fixed in version 8: https://github.com/redis/redis/issues/13207 + expect(await client.getrange(key, -200, -100)).toEqual( + cluster.checkIfServerVersionLessThan("8.0.0") ? "T" : "", + ); + + // empty key (returning null isn't implemented) + expect(await client.getrange(nonStringKey, 0, -1)).toEqual( + cluster.checkIfServerVersionLessThan("8.0.0") ? "" : null, + ); + + // non-string key + expect(await client.lpush(nonStringKey, ["_"])).toEqual(1); + await expect( + client.getrange(nonStringKey, 0, -1), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `lpush, lpop and lrange with key that holds a value that is not a list_%p`, + `testing hset and hget with multiple existing fields and one non existing field_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { const key = uuidv4(); - checkSimple(await client.set(key, "foo")).toEqual("OK"); - - try { - expect(await client.lpush(key, ["bar"])).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "Operation against a key holding the wrong kind of value", - ); - } + const field1 = uuidv4(); + const field2 = uuidv4(); + const value = uuidv4(); + const fieldValueList: HashDataType = [ + { + field: Buffer.from(field1), + value: Buffer.from(value), + }, + { + field: Buffer.from(field2), + value: Buffer.from(value), + }, + ]; - try { - expect(await client.lpop(key)).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "Operation against a key holding the wrong kind of value", - ); - } + const valueEncoded = Buffer.from(value); - try { - expect(await client.lrange(key, 0, -1)).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "Operation against a key holding the wrong kind of value", - ); - } + expect(await client.hset(key, fieldValueList)).toEqual(2); + expect( + await client.hget(Buffer.from(key), Buffer.from(field1)), + ).toEqual(value); + expect(await client.hget(key, field2)).toEqual(value); + expect(await client.hget(key, "nonExistingField")).toEqual( + null, + ); + + //hget with binary buffer + expect( + await client.hget(key, field1, { decoder: Decoder.Bytes }), + ).toEqual(valueEncoded); + expect( + await client.hget(key, field2, { decoder: Decoder.Bytes }), + ).toEqual(valueEncoded); + expect( + await client.hget(key, "nonExistingField", { + decoder: Decoder.Bytes, + }), + ).toEqual(null); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `llen with existing, non-existing key and key that holds a value that is not a list_%p`, + `testing hkeys with exiting, an non exising key and error request key_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = uuidv4(); + const key = uuidv4(); const key2 = uuidv4(); - const valueList = ["value4", "value3", "value2", "value1"]; - expect(await client.lpush(key1, valueList)).toEqual(4); - expect(await client.llen(key1)).toEqual(4); + const field1 = uuidv4(); + const field2 = uuidv4(); + const value = uuidv4(); + const value2 = uuidv4(); + const fieldValueMap = { + [field1]: value, + [field2]: value2, + }; + const field2Encoded = Buffer.from(field2); - expect(await client.llen("nonExistingKey")).toEqual(0); + // set up hash with two keys/values + expect(await client.hset(key, fieldValueMap)).toEqual(2); + expect(await client.hkeys(key)).toEqual([field1, field2]); + + // remove one key + expect(await client.hdel(key, [field1])).toEqual(1); + expect( + await client.hkeys(Buffer.from(key), { + decoder: Decoder.Bytes, + }), + ).toEqual([field2Encoded]); - checkSimple(await client.set(key2, "foo")).toEqual("OK"); + // non-existing key returns an empty list + expect(await client.hkeys("nonExistingKey")).toEqual([]); - try { - expect(await client.llen(key2)).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "Operation against a key holding the wrong kind of value", - ); - } + // Key exists, but it is not a hash + expect(await client.set(key2, value)).toEqual("OK"); + await expect(client.hkeys(key2)).rejects.toThrow(); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `lset test_%p`, + `hscan test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const nonExistingKey = uuidv4(); - const index = 0; - const oobIndex = 10; - const negativeIndex = -1; - const element = "zero"; - const lpushArgs = ["four", "three", "two", "one"]; - const expectedList = ["zero", "two", "three", "four"]; - const expectedList2 = ["zero", "two", "three", "zero"]; + const key1 = "{key}-1" + uuidv4(); + const initialCursor = "0"; + const defaultCount = 20; + const resultCursorIndex = 0; + const resultCollectionIndex = 1; - // key does not exist - await expect( - client.lset(nonExistingKey, index, element), - ).rejects.toThrow(RequestError); + // Setup test data - use a large number of entries to force an iterative cursor. + const numberMap: Record = {}; - expect(await client.lpush(key, lpushArgs)).toEqual(4); + for (let i = 0; i < 50000; i++) { + numberMap[i.toString()] = "num" + i; + } - // index out of range - await expect( - client.lset(key, oobIndex, element), - ).rejects.toThrow(RequestError); + const charMembers = ["a", "b", "c", "d", "e"]; + const charMap: Record = {}; - // assert lset result - checkSimple(await client.lset(key, index, element)).toEqual( - "OK", + for (let i = 0; i < charMembers.length; i++) { + charMap[charMembers[i]] = i.toString(); + } + + // Result contains the whole set + expect(await client.hset(key1, charMap)).toEqual( + charMembers.length, ); - checkSimple(await client.lrange(key, 0, negativeIndex)).toEqual( - expectedList, + let result = await client.hscan(key1, initialCursor); + expect(result[resultCursorIndex]).toEqual(initialCursor); + expect(result[resultCollectionIndex].length).toEqual( + Object.keys(charMap).length * 2, // Length includes the score which is twice the map size ); - // assert lset with a negative index for the last element in the list - checkSimple( - await client.lset(key, negativeIndex, element), - ).toEqual("OK"); - checkSimple(await client.lrange(key, 0, negativeIndex)).toEqual( - expectedList2, + const resultArray = result[resultCollectionIndex]; + const resultKeys = []; + const resultValues: string[] = []; + + for (let i = 0; i < resultArray.length; i += 2) { + resultKeys.push(resultArray[i]); + resultValues.push(resultArray[i + 1]); + } + + // Verify if all keys from charMap are in resultKeys + const allKeysIncluded = resultKeys.every( + (key) => key in charMap, ); + expect(allKeysIncluded).toEqual(true); - // assert lset against a non-list key - const nonListKey = "nonListKey"; - expect(await client.sadd(nonListKey, ["a"])).toEqual(1); + const allValuesIncluded = Object.values(charMap).every( + (value) => value in resultValues, + ); + expect(allValuesIncluded).toEqual(true); - await expect(client.lset(nonListKey, 0, "b")).rejects.toThrow( - RequestError, + // Test hscan with match + result = await client.hscan(key1, initialCursor, { + match: "a", + }); + + expect(result[resultCursorIndex]).toEqual(initialCursor); + expect(result[resultCollectionIndex]).toEqual(["a", "0"]); + + // Set up testing data with the numberMap set to be used for the next set test keys and test results. + expect(await client.hset(key1, numberMap)).toEqual( + Object.keys(numberMap).length, ); + + let resultCursor = initialCursor; + const secondResultAllKeys: string[] = []; + const secondResultAllValues: string[] = []; + let isFirstLoop = true; + + do { + result = await client.hscan(key1, resultCursor); + resultCursor = result[resultCursorIndex].toString(); + const resultEntry = result[resultCollectionIndex]; + + for (let i = 0; i < resultEntry.length; i += 2) { + secondResultAllKeys.push(resultEntry[i]); + secondResultAllValues.push(resultEntry[i + 1]); + } + + if (isFirstLoop) { + expect(resultCursor).not.toBe("0"); + isFirstLoop = false; + } else if (resultCursor === initialCursor) { + break; + } + + // Scan with result cursor has a different set + const secondResult = await client.hscan(key1, resultCursor); + const newResultCursor = + secondResult[resultCursorIndex].toString(); + expect(resultCursor).not.toBe(newResultCursor); + resultCursor = newResultCursor; + const secondResultEntry = + secondResult[resultCollectionIndex]; + + expect(result[resultCollectionIndex]).not.toBe( + secondResult[resultCollectionIndex], + ); + + for (let i = 0; i < secondResultEntry.length; i += 2) { + secondResultAllKeys.push(secondResultEntry[i]); + secondResultAllValues.push(secondResultEntry[i + 1]); + } + } while (resultCursor != initialCursor); // 0 is returned for the cursor of the last iteration. + + // Verify all data is found in hscan + const allSecondResultKeys = Object.keys(numberMap).every( + (key) => key in secondResultAllKeys, + ); + expect(allSecondResultKeys).toEqual(true); + + const allSecondResultValues = Object.keys(numberMap).every( + (value) => value in secondResultAllValues, + ); + expect(allSecondResultValues).toEqual(true); + + // Test match pattern + result = await client.hscan(key1, initialCursor, { + match: "*", + }); + expect(result[resultCursorIndex]).not.toEqual(initialCursor); + expect( + result[resultCollectionIndex].length, + ).toBeGreaterThanOrEqual(defaultCount); + + // Test count + result = await client.hscan(key1, initialCursor, { + count: 25, + }); + expect(result[resultCursorIndex]).not.toEqual(initialCursor); + expect( + result[resultCollectionIndex].length, + ).toBeGreaterThanOrEqual(25); + + // Test count with match returns a non-empty list + result = await client.hscan(key1, initialCursor, { + match: "1*", + count: 30, + }); + expect(result[resultCursorIndex]).not.toEqual(initialCursor); + expect(result[resultCollectionIndex].length).toBeGreaterThan(0); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `ltrim with existing key and key that holds a value that is not a list_%p`, + `hscan and sscan empty set, negative cursor, negative count, and non-hash key exception tests`, async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const valueList = ["value4", "value3", "value2", "value1"]; - expect(await client.lpush(key, valueList)).toEqual(4); - checkSimple(await client.ltrim(key, 0, 1)).toEqual("OK"); - checkSimple(await client.lrange(key, 0, -1)).toEqual([ - "value1", - "value2", - ]); - - /// `start` is greater than `end` so the key will be removed. - checkSimple(await client.ltrim(key, 4, 2)).toEqual("OK"); - expect(await client.lrange(key, 0, -1)).toEqual([]); - - checkSimple(await client.set(key, "foo")).toEqual("OK"); + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const initialCursor = "0"; + const resultCursorIndex = 0; + const resultCollectionIndex = 1; + + // Empty set + let result = await client.hscan(key1, initialCursor); + expect(result[resultCursorIndex]).toEqual(initialCursor); + expect(result[resultCollectionIndex]).toEqual([]); + + let result2 = await client.sscan(key1, initialCursor); + expect(result2[resultCursorIndex]).toEqual(initialCursor); + expect(result2[resultCollectionIndex]).toEqual([]); + + // Negative cursor + if (cluster.checkIfServerVersionLessThan("7.9.0")) { + result = await client.hscan(key1, "-1"); + expect(result[resultCursorIndex]).toEqual(initialCursor); + expect(result[resultCollectionIndex]).toEqual([]); + + result2 = await client.sscan(key1, "-1"); + expect(result2[resultCursorIndex]).toEqual(initialCursor); + expect(result2[resultCollectionIndex]).toEqual([]); + } else { + await expect(client.hscan(key1, "-1")).rejects.toThrow( + RequestError, + ); - try { - expect(await client.ltrim(key, 0, 1)).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "Operation against a key holding the wrong kind of value", + await expect(client.sscan(key1, "-1")).rejects.toThrow( + RequestError, ); } + + // Exceptions + // Non-hash key + expect(await client.set(key2, "test")).toEqual("OK"); + await expect(client.hscan(key2, initialCursor)).rejects.toThrow( + RequestError, + ); + await expect( + client.hscan(key2, initialCursor, { + match: "test", + count: 20, + }), + ).rejects.toThrow(RequestError); + + await expect(client.sscan(key2, initialCursor)).rejects.toThrow( + RequestError, + ); + await expect( + client.sscan(key2, initialCursor, { + match: "test", + count: 30, + }), + ).rejects.toThrow(RequestError); + + // Negative count + await expect( + client.hscan(key2, initialCursor, { + count: -1, + }), + ).rejects.toThrow(RequestError); + + await expect( + client.sscan(key2, initialCursor, { + count: -1, + }), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `lrem with existing key and non existing key_%p`, + `encoder test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { const key = uuidv4(); - const valueList = [ - "value1", - "value2", - "value1", - "value1", - "value2", - ]; - expect(await client.lpush(key, valueList)).toEqual(5); - expect(await client.lrem(key, 2, "value1")).toEqual(2); - checkSimple(await client.lrange(key, 0, -1)).toEqual([ - "value2", - "value2", - "value1", - ]); - expect(await client.lrem(key, -1, "value2")).toEqual(1); - checkSimple(await client.lrange(key, 0, -1)).toEqual([ - "value2", - "value1", - ]); - expect(await client.lrem(key, 0, "value2")).toEqual(1); - checkSimple(await client.lrange(key, 0, -1)).toEqual([ - "value1", - ]); - expect(await client.lrem("nonExistingKey", 2, "value")).toEqual( - 0, + const value = uuidv4(); + const valueEncoded = Buffer.from(value); + + expect(await client.set(key, value)).toEqual("OK"); + expect(await client.get(key)).toEqual(value); + expect(await client.get(key, Decoder.Bytes)).toEqual( + valueEncoded, + ); + expect(await client.get(key, Decoder.String)).toEqual(value); + + // Setting the encoded value. Should behave as the previous test since the default is String decoding. + expect(await client.set(key, valueEncoded)).toEqual("OK"); + expect(await client.get(key)).toEqual(value); + expect(await client.get(key, Decoder.Bytes)).toEqual( + valueEncoded, ); + expect(await client.get(key, Decoder.String)).toEqual(value); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `rpush and rpop with existing and non existing key_%p`, + `hdel multiple existing fields, an non existing field and an non existing key_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { const key = uuidv4(); - const valueList = ["value1", "value2", "value3", "value4"]; - expect(await client.rpush(key, valueList)).toEqual(4); - checkSimple(await client.rpop(key)).toEqual("value4"); - checkSimple(await client.rpopCount(key, 2)).toEqual([ - "value3", - "value2", - ]); - expect(await client.rpop("nonExistingKey")).toEqual(null); + const field1 = uuidv4(); + const field2 = uuidv4(); + const field3 = uuidv4(); + const value = uuidv4(); + const fieldValueMap = { + [field1]: value, + [field2]: value, + [field3]: value, + }; + + expect(await client.hset(key, fieldValueMap)).toEqual(3); + expect( + await client.hdel(Buffer.from(key), [field1, field2]), + ).toEqual(2); + expect(await client.hdel(key, ["nonExistingField"])).toEqual(0); + expect(await client.hdel("nonExistingKey", [field3])).toEqual( + 0, + ); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `rpush and rpop with key that holds a value that is not a list_%p`, + `testing hmget with multiple existing fields, an non existing field and an non existing key_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { const key = uuidv4(); - checkSimple(await client.set(key, "foo")).toEqual("OK"); - - try { - expect(await client.rpush(key, ["bar"])).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "Operation against a key holding the wrong kind of value", - ); - } - - try { - expect(await client.rpop(key)).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "Operation against a key holding the wrong kind of value", - ); - } + const field1 = uuidv4(); + const field2 = uuidv4(); + const value = uuidv4(); + const fieldValueMap = { + [field1]: value, + [field2]: value, + }; + expect(await client.hset(key, fieldValueMap)).toEqual(2); + expect( + await client.hmget(key, [ + field1, + "nonExistingField", + field2, + ]), + ).toEqual([value, null, value]); + expect( + await client.hmget( + Buffer.from("nonExistingKey"), + [field1, field2], + { decoder: Decoder.Bytes }, + ), + ).toEqual([null, null]); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `sadd, srem, scard and smembers with existing set_%p`, + `hexists existing field, an non existing field and an non existing key_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { const key = uuidv4(); - const valueList = ["member1", "member2", "member3", "member4"]; - expect(await client.sadd(key, valueList)).toEqual(4); + const field1 = uuidv4(); + const field2 = uuidv4(); + const fieldValueMap = { + [field1]: "value1", + [field2]: "value2", + }; + expect(await client.hset(key, fieldValueMap)).toEqual(2); expect( - await client.srem(key, ["member3", "nonExistingMember"]), - ).toEqual(1); - /// compare the 2 sets. - checkSimple(await client.smembers(key)).toEqual( - new Set(["member1", "member2", "member4"]), + await client.hexists(Buffer.from(key), Buffer.from(field1)), + ).toEqual(true); + expect(await client.hexists(key, "nonExistingField")).toEqual( + false, + ); + expect(await client.hexists("nonExistingKey", field2)).toEqual( + false, ); - expect(await client.srem(key, ["member1"])).toEqual(1); - expect(await client.scard(key)).toEqual(2); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `smove test_%p`, + `hgetall with multiple fields in an existing key and one non existing key_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = "{key}" + uuidv4(); - const key2 = "{key}" + uuidv4(); - const key3 = "{key}" + uuidv4(); - const string_key = "{key}" + uuidv4(); - const non_existing_key = "{key}" + uuidv4(); - - expect(await client.sadd(key1, ["1", "2", "3"])).toEqual(3); - expect(await client.sadd(key2, ["2", "3"])).toEqual(2); - - // move an element - expect(await client.smove(key1, key2, "1")); - checkSimple(await client.smembers(key1)).toEqual( - new Set(["2", "3"]), - ); - checkSimple(await client.smembers(key2)).toEqual( - new Set(["1", "2", "3"]), - ); - - // moved element already exists in the destination set - expect(await client.smove(key2, key1, "2")); - checkSimple(await client.smembers(key1)).toEqual( - new Set(["2", "3"]), - ); - checkSimple(await client.smembers(key2)).toEqual( - new Set(["1", "3"]), - ); - - // attempt to move from a non-existing key - expect(await client.smove(non_existing_key, key1, "4")).toEqual( - false, - ); - checkSimple(await client.smembers(key1)).toEqual( - new Set(["2", "3"]), - ); - - // move to a new set - expect(await client.smove(key1, key3, "2")); - checkSimple(await client.smembers(key1)).toEqual( - new Set(["3"]), - ); - checkSimple(await client.smembers(key3)).toEqual( - new Set(["2"]), - ); + const key = uuidv4(); + const field1 = uuidv4(); + const field2 = uuidv4(); + const value = uuidv4(); + const fieldValueMap = { + [field1]: value, + [field2]: value, + }; + expect(await client.hset(key, fieldValueMap)).toEqual(2); - // attempt to move a missing element - expect(await client.smove(key1, key3, "42")).toEqual(false); - checkSimple(await client.smembers(key1)).toEqual( - new Set(["3"]), - ); - checkSimple(await client.smembers(key3)).toEqual( - new Set(["2"]), - ); + expect(await client.hgetall(key)).toEqual({ + [field1]: value, + [field2]: value, + }); - // move missing element to missing key expect( - await client.smove(key1, non_existing_key, "42"), - ).toEqual(false); - checkSimple(await client.smembers(key1)).toEqual( - new Set(["3"]), - ); - checkSimple(await client.type(non_existing_key)).toEqual( - "none", - ); + await client.hgetall(Buffer.from("nonExistingKey")), + ).toEqual({}); + }, protocol); + }, + config.timeout, + ); - // key exists, but it is not a set - checkSimple(await client.set(string_key, "value")).toEqual( - "OK", - ); - await expect( - client.smove(string_key, key1, "_"), - ).rejects.toThrow(); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `hincrBy and hincrByFloat with existing key and field_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const field = uuidv4(); + const fieldValueMap = { + [field]: "10", + }; + expect(await client.hset(key, fieldValueMap)).toEqual(1); + expect(await client.hincrBy(key, field, 1)).toEqual(11); + expect( + await client.hincrBy( + Buffer.from(key), + Buffer.from(field), + 4, + ), + ).toEqual(15); + expect( + await client.hincrByFloat( + Buffer.from(key), + Buffer.from(field), + 1.5, + ), + ).toEqual(16.5); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `srem, scard and smembers with non existing key_%p`, + `hincrBy and hincrByFloat with non existing key and non existing field_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - expect(await client.srem("nonExistingKey", ["member"])).toEqual( - 0, - ); - expect(await client.scard("nonExistingKey")).toEqual(0); - expect(await client.smembers("nonExistingKey")).toEqual( - new Set(), - ); + const key1 = uuidv4(); + const key2 = uuidv4(); + const field = uuidv4(); + const fieldValueMap = { + [field]: "10", + }; + expect( + await client.hincrBy("nonExistingKey", field, 1), + ).toEqual(1); + expect(await client.hset(key1, fieldValueMap)).toEqual(1); + expect( + await client.hincrBy(key1, "nonExistingField", 2), + ).toEqual(2); + expect(await client.hset(key2, fieldValueMap)).toEqual(1); + expect( + await client.hincrByFloat(key2, "nonExistingField", -0.5), + ).toEqual(-0.5); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `sadd, srem, scard and smembers with with key that holds a value that is not a set_%p`, + `hincrBy and hincrByFloat with a field that contains a value of string that can not be represented as as integer or float_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { const key = uuidv4(); - checkSimple(await client.set(key, "foo")).toEqual("OK"); - - try { - expect(await client.sadd(key, ["bar"])).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "Operation against a key holding the wrong kind of value", - ); - } - - try { - expect(await client.srem(key, ["bar"])).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "Operation against a key holding the wrong kind of value", - ); - } + const field = uuidv4(); + const fieldValueMap = { + [field]: "foo", + }; + expect(await client.hset(key, fieldValueMap)).toEqual(1); try { - expect(await client.scard(key)).toThrow(); + expect(await client.hincrBy(key, field, 2)).toThrow(); } catch (e) { expect((e as Error).message).toMatch( - "Operation against a key holding the wrong kind of value", + "hash value is not an integer", ); } try { - expect(await client.smembers(key)).toThrow(); + expect( + await client.hincrByFloat(key, field, 1.5), + ).toThrow(); } catch (e) { expect((e as Error).message).toMatch( - "Operation against a key holding the wrong kind of value", + "hash value is not a float", ); } }, protocol); @@ -1201,303 +1868,238 @@ export function runBaseTests(config: { ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `sinter test_%p`, + `hlen test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = `{key}-1-${uuidv4()}`; - const key2 = `{key}-2-${uuidv4()}`; - const non_existing_key = `{key}`; - const member1_list = ["a", "b", "c", "d"]; - const member2_list = ["c", "d", "e"]; - - // positive test case - expect(await client.sadd(key1, member1_list)).toEqual(4); - expect(await client.sadd(key2, member2_list)).toEqual(3); - checkSimple(await client.sinter([key1, key2])).toEqual( - new Set(["c", "d"]), - ); - - // invalid argument - key list must not be empty - try { - expect(await client.sinter([])).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "ResponseError: wrong number of arguments", - ); - } - - // non-existing key returns empty set - expect(await client.sinter([key1, non_existing_key])).toEqual( - new Set(), - ); - - // non-set key - checkSimple(await client.set(key2, "value")).toEqual("OK"); + const key1 = uuidv4(); + const field1 = uuidv4(); + const field2 = uuidv4(); + const fieldValueList = [ + { + field: field1, + value: "value1", + }, + { + field: field2, + value: "value2", + }, + ]; - try { - expect(await client.sinter([key2])).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "Operation against a key holding the wrong kind of value", - ); - } + expect(await client.hset(key1, fieldValueList)).toEqual(2); + expect(await client.hlen(key1)).toEqual(2); + expect(await client.hdel(key1, [field1])).toEqual(1); + expect(await client.hlen(Buffer.from(key1))).toEqual(1); + expect(await client.hlen("nonExistingHash")).toEqual(0); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `sinterstore test_%p`, + `hvals test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = `{key}-1-${uuidv4()}`; - const key2 = `{key}-2-${uuidv4()}`; - const key3 = `{key}-3-${uuidv4()}`; - const nonExistingKey = `{key}-4-${uuidv4()}`; - const stringKey = `{key}-5-${uuidv4()}`; - const member1_list = ["a", "b", "c"]; - const member2_list = ["c", "d", "e"]; - - expect(await client.sadd(key1, member1_list)).toEqual(3); - expect(await client.sadd(key2, member2_list)).toEqual(3); + const key1 = uuidv4(); + const key2 = uuidv4(); + const field1 = uuidv4(); + const field2 = uuidv4(); + const fieldValueMap = { + [field1]: "value1", + [field2]: "value2", + }; + const value1Encoded = Buffer.from("value1"); + const value2Encoded = Buffer.from("value2"); - // store in a new key - expect(await client.sinterstore(key3, [key1, key2])).toEqual(1); - checkSimple(await client.smembers(key3)).toEqual( - new Set(["c"]), - ); + expect( + await client.hset(Buffer.from(key1), fieldValueMap), + ).toEqual(2); + expect(await client.hvals(key1)).toEqual(["value1", "value2"]); + expect(await client.hdel(key1, [field1])).toEqual(1); + expect(await client.hvals(Buffer.from(key1))).toEqual([ + "value2", + ]); + expect(await client.hvals("nonExistingHash")).toEqual([]); - // overwrite existing set, which is also a source set - expect(await client.sinterstore(key2, [key2, key3])).toEqual(1); - checkSimple(await client.smembers(key2)).toEqual( - new Set(["c"]), - ); + //hvals with binary buffers + expect(await client.hset(key2, fieldValueMap)).toEqual(2); + expect(await client.hvals(key2, Decoder.Bytes)).toEqual([ + value1Encoded, + value2Encoded, + ]); + expect(await client.hdel(key2, [field1])).toEqual(1); + expect(await client.hvals(key2, Decoder.Bytes)).toEqual([ + value2Encoded, + ]); + }, protocol); + }, + config.timeout, + ); - // source set is the same as the existing set - expect(await client.sinterstore(key2, [key2])).toEqual(1); - checkSimple(await client.smembers(key2)).toEqual( - new Set(["c"]), - ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `hsetnx test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + const field = uuidv4(); - // intersection with non-existing key + expect(await client.hsetnx(key1, field, "value")).toEqual(true); expect( - await client.sinterstore(key1, [key2, nonExistingKey]), - ).toEqual(0); - checkSimple(await client.smembers(key1)).toEqual(new Set()); - - // invalid argument - key list must not be empty - await expect(client.sinterstore(key3, [])).rejects.toThrow(); + await client.hsetnx( + Buffer.from(key1), + Buffer.from(field), + "newValue", + ), + ).toEqual(false); + expect(await client.hget(key1, field)).toEqual("value"); - // non-set key - checkSimple(await client.set(stringKey, "foo")).toEqual("OK"); + expect(await client.set(key2, "value")).toEqual("OK"); await expect( - client.sinterstore(key3, [stringKey]), + client.hsetnx(key2, field, "value"), ).rejects.toThrow(); - - // overwrite non-set key - expect(await client.sinterstore(stringKey, [key2])).toEqual(1); - checkSimple(await client.smembers(stringKey)).toEqual( - new Set("c"), - ); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `sdiff test_%p`, + `hstrlen test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = `{key}-1-${uuidv4()}`; - const key2 = `{key}-2-${uuidv4()}`; - const stringKey = `{key}-3-${uuidv4()}`; - const nonExistingKey = `{key}-4-${uuidv4()}`; - const member1_list = ["a", "b", "c"]; - const member2_list = ["c", "d", "e"]; + const key1 = uuidv4(); + const key2 = uuidv4(); + const field = uuidv4(); - expect(await client.sadd(key1, member1_list)).toEqual(3); - expect(await client.sadd(key2, member2_list)).toEqual(3); + expect(await client.hset(key1, { field: "value" })).toBe(1); + expect(await client.hstrlen(key1, "field")).toBe(5); - checkSimple(await client.sdiff([key1, key2])).toEqual( - new Set(["a", "b"]), - ); - checkSimple(await client.sdiff([key2, key1])).toEqual( - new Set(["d", "e"]), - ); + // missing value + expect(await client.hstrlen(key1, "nonExistingField")).toBe(0); - checkSimple(await client.sdiff([key1, nonExistingKey])).toEqual( - new Set(["a", "b", "c"]), - ); - checkSimple(await client.sdiff([nonExistingKey, key1])).toEqual( - new Set(), - ); - - // invalid arg - key list must not be empty - await expect(client.sdiff([])).rejects.toThrow(); + // missing key + expect(await client.hstrlen(key2, "field")).toBe(0); - // key exists, but it is not a set - checkSimple(await client.set(stringKey, "foo")).toEqual("OK"); - await expect(client.sdiff([stringKey])).rejects.toThrow(); + // key exists but holds non hash type value + expect(await client.set(key2, "value")).toEqual("OK"); + await expect( + client.hstrlen(Buffer.from(key2), Buffer.from(field)), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `sdiffstore test_%p`, + `hrandfield test_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { - const key1 = `{key}-1-${uuidv4()}`; - const key2 = `{key}-2-${uuidv4()}`; - const key3 = `{key}-3-${uuidv4()}`; - const stringKey = `{key}-4-${uuidv4()}`; - const nonExistingKey = `{key}-5-${uuidv4()}`; - const member1_list = ["a", "b", "c"]; - const member2_list = ["c", "d", "e"]; + await runTest(async (client: BaseClient, cluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) { + return; + } - expect(await client.sadd(key1, member1_list)).toEqual(3); - expect(await client.sadd(key2, member2_list)).toEqual(3); + const key1 = uuidv4(); + const key2 = uuidv4(); - // store diff in new key - expect(await client.sdiffstore(key3, [key1, key2])).toEqual(2); - checkSimple(await client.smembers(key3)).toEqual( - new Set(["a", "b"]), + // key does not exist + expect( + await client.hrandfield(Buffer.from(key1), { + decoder: Decoder.Bytes, + }), + ).toBeNull(); + expect(await client.hrandfieldCount(key1, 5)).toEqual([]); + expect(await client.hrandfieldWithValues(key1, 5)).toEqual([]); + + const data = { "f 1": "v 1", "f 2": "v 2", "f 3": "v 3" }; + const fields = Object.keys(data); + const entries = Object.entries(data); + const encodedFields = fields.map(Buffer.from); + const encodedEntries = entries.map((e) => e.map(Buffer.from)); + expect(await client.hset(key1, data)).toEqual(3); + + expect(fields).toContain(await client.hrandfield(key1)); + + // With Count - positive count + let result = await client.hrandfieldCount(key1, 5); + expect(result).toEqual(fields); + + // With Count - negative count + result = await client.hrandfieldCount(Buffer.from(key1), -5, { + decoder: Decoder.Bytes, + }); + expect(result.length).toEqual(5); + result.map((r) => expect(encodedFields).toContainEqual(r)); + + // With values - positive count + let result2 = await client.hrandfieldWithValues( + Buffer.from(key1), + 5, + { decoder: Decoder.Bytes }, ); + expect(result2).toEqual(encodedEntries); - // overwrite existing set - expect(await client.sdiffstore(key3, [key2, key1])).toEqual(2); - checkSimple(await client.smembers(key3)).toEqual( - new Set(["d", "e"]), - ); + // With values - negative count + result2 = await client.hrandfieldWithValues(key1, -5); + expect(result2.length).toEqual(5); + result2.map((r) => expect(entries).toContainEqual(r)); - // overwrite one of the source sets - expect(await client.sdiffstore(key3, [key2, key3])).toEqual(1); - checkSimple(await client.smembers(key3)).toEqual( - new Set(["c"]), + // key exists but holds non hash type value + expect(await client.set(key2, "value")).toEqual("OK"); + await expect(client.hrandfield(key2)).rejects.toThrow( + RequestError, ); - - // diff between non-empty set and empty set - expect( - await client.sdiffstore(key3, [key1, nonExistingKey]), - ).toEqual(3); - checkSimple(await client.smembers(key3)).toEqual( - new Set(["a", "b", "c"]), + await expect(client.hrandfieldCount(key2, 42)).rejects.toThrow( + RequestError, ); - - // diff between empty set and non-empty set - expect( - await client.sdiffstore(key3, [nonExistingKey, key1]), - ).toEqual(0); - checkSimple(await client.smembers(key3)).toEqual(new Set()); - - // invalid argument - key list must not be empty - await expect(client.sdiffstore(key3, [])).rejects.toThrow(); - - // source key exists, but it is not a set - checkSimple(await client.set(stringKey, "foo")).toEqual("OK"); await expect( - client.sdiffstore(key3, [stringKey]), - ).rejects.toThrow(); - - // overwrite a key holding a non-set value - expect( - await client.sdiffstore(stringKey, [key1, key2]), - ).toEqual(2); - checkSimple(await client.smembers(stringKey)).toEqual( - new Set(["a", "b"]), - ); + client.hrandfieldWithValues(key2, 42), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `sunion test_%p`, + `lpush, lpop and lrange with existing and non existing key_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = `{key}:${uuidv4()}`; - const key2 = `{key}:${uuidv4()}`; - const stringKey = `{key}:${uuidv4()}`; - const nonExistingKey = `{key}:${uuidv4()}`; - const memberList1 = ["a", "b", "c"]; - const memberList2 = ["b", "c", "d", "e"]; - - expect(await client.sadd(key1, memberList1)).toEqual(3); - expect(await client.sadd(key2, memberList2)).toEqual(4); - checkSimple(await client.sunion([key1, key2])).toEqual( - new Set(["a", "b", "c", "d", "e"]), + const key1 = uuidv4(); + const key2 = Buffer.from(uuidv4()); + const valueList1 = ["value4", "value3", "value2", "value1"]; + const valueList2 = ["value7", "value6", "value5"]; + const encodedValues = [ + Buffer.from("value6"), + Buffer.from("value7"), + ]; + expect(await client.lpush(key1, valueList1)).toEqual(4); + expect(await client.lpop(key1)).toEqual("value1"); + expect(await client.lrange(key1, 0, -1)).toEqual([ + "value2", + "value3", + "value4", + ]); + expect(await client.lpopCount(key1, 2)).toEqual([ + "value2", + "value3", + ]); + expect(await client.lrange("nonExistingKey", 0, -1)).toEqual( + [], ); - - // invalid argument - key list must not be empty - await expect(client.sunion([])).rejects.toThrow(); - - // non-existing key returns the set of existing keys - checkSimple( - await client.sunion([key1, nonExistingKey]), - ).toEqual(new Set(memberList1)); - - // key exists, but it is not a set - checkSimple(await client.set(stringKey, "foo")).toEqual("OK"); - await expect(client.sunion([stringKey])).rejects.toThrow(); - }, protocol); - }, - config.timeout, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `sunionstore test_%p`, - async (protocol) => { - await runTest(async (client: BaseClient) => { - const key1 = `{key}:${uuidv4()}`; - const key2 = `{key}:${uuidv4()}`; - const key3 = `{key}:${uuidv4()}`; - const key4 = `{key}:${uuidv4()}`; - const stringKey = `{key}:${uuidv4()}`; - const nonExistingKey = `{key}:${uuidv4()}`; - - expect(await client.sadd(key1, ["a", "b", "c"])).toEqual(3); - expect(await client.sadd(key2, ["c", "d", "e"])).toEqual(3); - expect(await client.sadd(key3, ["e", "f", "g"])).toEqual(3); - - // store union in new key - expect(await client.sunionstore(key4, [key1, key2])).toEqual(5); - checkSimple(await client.smembers(key4)).toEqual( - new Set(["a", "b", "c", "d", "e"]), + expect(await client.lpop("nonExistingKey")).toEqual(null); + expect(await client.lpush(key2, valueList2)).toEqual(3); + expect(await client.lpop(key2, Decoder.Bytes)).toEqual( + Buffer.from("value5"), ); - - // overwrite existing set - expect(await client.sunionstore(key1, [key4, key2])).toEqual(5); - checkSimple(await client.smembers(key1)).toEqual( - new Set(["a", "b", "c", "d", "e"]), + expect(await client.lrange(key2, 0, -1, Decoder.Bytes)).toEqual( + encodedValues, ); - - // overwrite one of the source keys - expect(await client.sunionstore(key2, [key4, key2])).toEqual(5); - checkSimple(await client.smembers(key2)).toEqual( - new Set(["a", "b", "c", "d", "e"]), + expect(await client.lpopCount(key2, 2, Decoder.Bytes)).toEqual( + encodedValues, ); - - // union with a non-existing key - expect( - await client.sunionstore(key2, [nonExistingKey]), - ).toEqual(0); - expect(await client.smembers(key2)).toEqual(new Set()); - - // invalid argument - key list must not be empty - await expect(client.sunionstore(key4, [])).rejects.toThrow(); - - // key exists, but it is not a set - checkSimple(await client.set(stringKey, "foo")).toEqual("OK"); - await expect( - client.sunionstore(key4, [stringKey, key1]), - ).rejects.toThrow(); - - // overwrite destination when destination is not a set expect( - await client.sunionstore(stringKey, [key1, key3]), - ).toEqual(7); - checkSimple(await client.smembers(stringKey)).toEqual( - new Set(["a", "b", "c", "d", "e", "f", "g"]), + await client.lpush(key2, [Buffer.from("value8")]), + ).toEqual(1); + expect(await client.lpop(key2, Decoder.Bytes)).toEqual( + Buffer.from("value8"), ); }, protocol); }, @@ -1505,1106 +2107,1397 @@ export function runBaseTests(config: { ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `sismember test_%p`, + `lpush, lpop and lrange with key that holds a value that is not a list_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = uuidv4(); - const key2 = uuidv4(); - expect(await client.sadd(key1, ["member1"])).toEqual(1); - expect(await client.sismember(key1, "member1")).toEqual(true); - expect( - await client.sismember(key1, "nonExistingMember"), - ).toEqual(false); - expect( - await client.sismember("nonExistingKey", "member1"), - ).toEqual(false); + const key = uuidv4(); + expect(await client.set(key, "foo")).toEqual("OK"); - checkSimple(await client.set(key2, "foo")).toEqual("OK"); - await expect( - client.sismember(key2, "member1"), - ).rejects.toThrow(); + try { + expect(await client.lpush(key, ["bar"])).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value", + ); + } + + try { + expect(await client.lpop(key)).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value", + ); + } + + try { + expect(await client.lrange(key, 0, -1)).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value", + ); + } }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `spop and spopCount test_%p`, + `lpushx list_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - let members = ["member1", "member2", "member3"]; - expect(await client.sadd(key, members)).toEqual(3); + const key1 = uuidv4(); + const key2 = uuidv4(); + const key3 = uuidv4(); + expect(await client.lpush(key1, ["0"])).toEqual(1); + expect(await client.lpushx(key1, ["1", "2", "3"])).toEqual(4); + expect(await client.lrange(key1, 0, -1)).toEqual([ + "3", + "2", + "1", + "0", + ]); - const result1 = await client.spop(key); - expect(members).toContain(intoString(result1)); + expect(await client.lpushx(key2, ["1"])).toEqual(0); + expect(await client.lrange(key2, 0, -1)).toEqual([]); - members = members.filter((item) => item != result1); - const result2 = await client.spopCount(key, 2); - expect(intoString(result2)).toEqual(intoString(members)); - expect(await client.spop("nonExistingKey")).toEqual(null); - expect(await client.spopCount("nonExistingKey", 1)).toEqual( - new Set(), + // Key exists, but is not a list + expect(await client.set(key3, "bar")); + await expect(client.lpushx(key3, ["_"])).rejects.toThrow( + RequestError, + ); + + // Empty element list + await expect(client.lpushx(key2, [])).rejects.toThrow( + RequestError, ); + + // test for binary key as input + const key4 = uuidv4(); + expect(await client.lpush(key4, ["0"])).toEqual(1); + expect( + await client.lpushx(Buffer.from(key4), [ + Buffer.from("1"), + Buffer.from("2"), + Buffer.from("3"), + ]), + ).toEqual(4); + expect(await client.lrange(key4, 0, -1)).toEqual([ + "3", + "2", + "1", + "0", + ]); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `exists with existing keys, an non existing key_%p`, + `llen with existing, non-existing key and key that holds a value that is not a list_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { const key1 = uuidv4(); const key2 = uuidv4(); - const value = uuidv4(); - checkSimple(await client.set(key1, value)).toEqual("OK"); - expect(await client.exists([key1])).toEqual(1); - checkSimple(await client.set(key2, value)).toEqual("OK"); - expect( - await client.exists([key1, "nonExistingKey", key2]), - ).toEqual(2); - expect(await client.exists([key1, key1])).toEqual(2); + const valueList = ["value4", "value3", "value2", "value1"]; + expect(await client.lpush(key1, valueList)).toEqual(4); + expect(await client.llen(key1)).toEqual(4); + expect(await client.llen(Buffer.from(key1))).toEqual(4); + + expect(await client.llen("nonExistingKey")).toEqual(0); + + expect(await client.set(key2, "foo")).toEqual("OK"); + + try { + expect(await client.llen(key2)).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value", + ); + } }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `unlink multiple existing keys and an non existing key_%p`, + `lmove list_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { - const key1 = "{key}" + uuidv4(); - const key2 = "{key}" + uuidv4(); - const key3 = "{key}" + uuidv4(); - const value = uuidv4(); - checkSimple(await client.set(key1, value)).toEqual("OK"); - checkSimple(await client.set(key2, value)).toEqual("OK"); - checkSimple(await client.set(key3, value)).toEqual("OK"); + await runTest(async (client: BaseClient, cluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) { + return; + } + + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const key1Encoded = Buffer.from("{key}-1" + uuidv4()); + const key2Encoded = Buffer.from("{key}-2" + uuidv4()); + const lpushArgs1 = ["2", "1"]; + const lpushArgs2 = ["4", "3"]; + + // Initialize the tests + expect(await client.lpush(key1, lpushArgs1)).toEqual(2); + expect(await client.lpush(key2, lpushArgs2)).toEqual(2); + expect(await client.lpush(key1Encoded, lpushArgs1)).toEqual(2); + expect(await client.lpush(key2Encoded, lpushArgs2)).toEqual(2); + + // Move from LEFT to LEFT expect( - await client.unlink([key1, key2, "nonExistingKey", key3]), - ).toEqual(3); - }, protocol); - }, - config.timeout, - ); + await client.lmove( + key1, + key2, + ListDirection.LEFT, + ListDirection.LEFT, + ), + ).toEqual("1"); - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `expire, pexpire and ttl with positive timeout_%p`, - async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = uuidv4(); - checkSimple(await client.set(key, "foo")).toEqual("OK"); - expect(await client.expire(key, 10)).toEqual(true); - expect(await client.ttl(key)).toBeLessThanOrEqual(10); - /// set command clears the timeout. - checkSimple(await client.set(key, "bar")).toEqual("OK"); - const versionLessThan = - await checkIfServerVersionLessThan("7.0.0"); + // Move from LEFT to RIGHT + expect( + await client.lmove( + key1, + key2, + ListDirection.LEFT, + ListDirection.RIGHT, + ), + ).toEqual("2"); - if (versionLessThan) { - expect(await client.pexpire(key, 10000)).toEqual(true); - } else { - expect( - await client.pexpire( - key, - 10000, - ExpireOptions.HasNoExpiry, - ), - ).toEqual(true); - } + expect(await client.lrange(key2, 0, -1)).toEqual([ + "1", + "3", + "4", + "2", + ]); + expect(await client.lrange(key1, 0, -1)).toEqual([]); - expect(await client.ttl(key)).toBeLessThanOrEqual(10); + // Move from RIGHT to LEFT - non-existing destination key + expect( + await client.lmove( + key2, + key1, + ListDirection.RIGHT, + ListDirection.LEFT, + ), + ).toEqual("2"); - /// TTL will be updated to the new value = 15 - if (versionLessThan) { - expect(await client.expire(key, 15)).toEqual(true); - } else { - expect( - await client.expire( - key, - 15, - ExpireOptions.HasExistingExpiry, - ), - ).toEqual(true); - } + // Move from RIGHT to RIGHT + expect( + await client.lmove( + key2, + key1, + ListDirection.RIGHT, + ListDirection.RIGHT, + ), + ).toEqual("4"); - expect(await client.ttl(key)).toBeLessThanOrEqual(15); - }, protocol); - }, - config.timeout, - ); + expect(await client.lrange(key2, 0, -1)).toEqual(["1", "3"]); + expect(await client.lrange(key1, 0, -1)).toEqual(["2", "4"]); - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `expireAt, pexpireAt and ttl with positive timeout_%p`, - async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = uuidv4(); - checkSimple(await client.set(key, "foo")).toEqual("OK"); + // Move from RIGHT to LEFT with encoded return value expect( - await client.expireAt( - key, - Math.floor(Date.now() / 1000) + 10, + await client.lmove( + key1, + key2, + ListDirection.RIGHT, + ListDirection.LEFT, + Decoder.Bytes, ), - ).toEqual(true); - expect(await client.ttl(key)).toBeLessThanOrEqual(10); - const versionLessThan = - await checkIfServerVersionLessThan("7.0.0"); + ).toEqual(Buffer.from("4")); - if (versionLessThan) { - expect( - await client.expireAt( - key, - Math.floor(Date.now() / 1000) + 50, - ), - ).toEqual(true); - } else { - expect( - await client.expireAt( - key, - Math.floor(Date.now() / 1000) + 50, - ExpireOptions.NewExpiryGreaterThanCurrent, - ), - ).toEqual(true); - } + // Move from RIGHT to LEFT with encoded list keys + expect( + await client.lmove( + key1Encoded, + key2Encoded, + ListDirection.RIGHT, + ListDirection.LEFT, + ), + ).toEqual("2"); - expect(await client.ttl(key)).toBeLessThanOrEqual(50); + // Non-existing source key + expect( + await client.lmove( + "{key}-non_existing_key" + uuidv4(), + key1, + ListDirection.LEFT, + ListDirection.LEFT, + ), + ).toEqual(null); - /// set command clears the timeout. - checkSimple(await client.set(key, "bar")).toEqual("OK"); + // Non-list source key + const key3 = "{key}-3" + uuidv4(); + expect(await client.set(key3, "value")).toEqual("OK"); + await expect( + client.lmove( + key3, + key1, + ListDirection.LEFT, + ListDirection.LEFT, + ), + ).rejects.toThrow(RequestError); - if (!versionLessThan) { - expect( - await client.pexpireAt( - key, - Date.now() + 50000, - ExpireOptions.HasExistingExpiry, - ), - ).toEqual(false); - } + // Non-list destination key + await expect( + client.lmove( + key1, + key3, + ListDirection.LEFT, + ListDirection.LEFT, + ), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `expire, pexpire, expireAt and pexpireAt with timestamp in the past or negative timeout_%p`, + `blmove list_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = uuidv4(); - checkSimple(await client.set(key, "foo")).toEqual("OK"); - expect(await client.ttl(key)).toEqual(-1); - expect(await client.expire(key, -10)).toEqual(true); - expect(await client.ttl(key)).toEqual(-2); - checkSimple(await client.set(key, "foo")).toEqual("OK"); - expect(await client.pexpire(key, -10000)).toEqual(true); - expect(await client.ttl(key)).toEqual(-2); - checkSimple(await client.set(key, "foo")).toEqual("OK"); + await runTest(async (client: BaseClient, cluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) { + return; + } + + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const key1Encoded = Buffer.from("{key}-1" + uuidv4()); + const key2Encoded = Buffer.from("{key}-2" + uuidv4()); + const lpushArgs1 = ["2", "1"]; + const lpushArgs2 = ["4", "3"]; + + // Initialize the tests + expect(await client.lpush(key1, lpushArgs1)).toEqual(2); + expect(await client.lpush(key2, lpushArgs2)).toEqual(2); + expect(await client.lpush(key1Encoded, lpushArgs1)).toEqual(2); + expect(await client.lpush(key2Encoded, lpushArgs2)).toEqual(2); + + // Move from LEFT to LEFT with blocking expect( - await client.expireAt( - key, - Math.floor(Date.now() / 1000) - 50, /// timeout in the past + await client.blmove( + key1, + key2, + ListDirection.LEFT, + ListDirection.LEFT, + 0.1, ), - ).toEqual(true); - expect(await client.ttl(key)).toEqual(-2); - checkSimple(await client.set(key, "foo")).toEqual("OK"); + ).toEqual("1"); + + // Move from LEFT to RIGHT with blocking expect( - await client.pexpireAt( - key, - Date.now() - 50000, /// timeout in the past + await client.blmove( + key1, + key2, + ListDirection.LEFT, + ListDirection.RIGHT, + 0.1, ), - ).toEqual(true); - expect(await client.ttl(key)).toEqual(-2); - }, protocol); - }, - config.timeout, - ); + ).toEqual("2"); - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `expire, pexpire, expireAt, pexpireAt and ttl with non-existing key_%p`, - async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = uuidv4(); - expect(await client.expire(key, 10)).toEqual(false); - expect(await client.pexpire(key, 10000)).toEqual(false); + expect(await client.lrange(key2, 0, -1)).toEqual([ + "1", + "3", + "4", + "2", + ]); + expect(await client.lrange(key1, 0, -1)).toEqual([]); + + // Move from RIGHT to LEFT non-existing destination with blocking expect( - await client.expireAt( - key, - Math.floor(Date.now() / 1000) + 50, /// timeout in the past + await client.blmove( + key2, + key1, + ListDirection.RIGHT, + ListDirection.LEFT, + 0.1, ), - ).toEqual(false); + ).toEqual("2"); + + expect(await client.lrange(key2, 0, -1)).toEqual([ + "1", + "3", + "4", + ]); + expect(await client.lrange(key1, 0, -1)).toEqual(["2"]); + + // Move from RIGHT to RIGHT with blocking expect( - await client.pexpireAt( - key, - Date.now() + 50000, /// timeout in the past + await client.blmove( + key2, + key1, + ListDirection.RIGHT, + ListDirection.RIGHT, + 0.1, ), - ).toEqual(false); - expect(await client.ttl(key)).toEqual(-2); + ).toEqual("4"); + + expect(await client.lrange(key2, 0, -1)).toEqual(["1", "3"]); + expect(await client.lrange(key1, 0, -1)).toEqual(["2", "4"]); + + // Move from RIGHT to LEFT with blocking and encoded return value + expect( + await client.blmove( + key1, + key2, + ListDirection.RIGHT, + ListDirection.LEFT, + 0.1, + Decoder.Bytes, + ), + ).toEqual(Buffer.from("4")); + + // Move from RIGHT to LEFT with encoded list keys + expect( + await client.blmove( + key1Encoded, + key2Encoded, + ListDirection.RIGHT, + ListDirection.LEFT, + 0.1, + ), + ).toEqual("2"); + + // Non-existing source key with blocking + expect( + await client.blmove( + "{key}-non_existing_key" + uuidv4(), + key1, + ListDirection.LEFT, + ListDirection.LEFT, + 0.1, + ), + ).toEqual(null); + + // Non-list source key with blocking + const key3 = "{key}-3" + uuidv4(); + expect(await client.set(key3, "value")).toEqual("OK"); + await expect( + client.blmove( + key3, + key1, + ListDirection.LEFT, + ListDirection.LEFT, + 0.1, + ), + ).rejects.toThrow(RequestError); + + // Non-list destination key + await expect( + client.blmove( + key1, + key3, + ListDirection.LEFT, + ListDirection.LEFT, + 0.1, + ), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `script test_%p`, + `lset test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = Buffer.from(uuidv4()); - const key2 = Buffer.from(uuidv4()); + const key = uuidv4(); + const nonExistingKey = uuidv4(); + const index = 0; + const oobIndex = 10; + const negativeIndex = -1; + const element = "zero"; + const lpushArgs = ["four", "three", "two", "one"]; + const expectedList = ["zero", "two", "three", "four"]; + const expectedList2 = ["zero", "two", "three", "zero"]; - let script = new Script(Buffer.from("return 'Hello'")); - checkSimple(await client.invokeScript(script)).toEqual("Hello"); + // key does not exist + await expect( + client.lset(nonExistingKey, index, element), + ).rejects.toThrow(RequestError); - script = new Script( - Buffer.from("return redis.call('SET', KEYS[1], ARGV[1])"), + expect(await client.lpush(key, lpushArgs)).toEqual(4); + + // index out of range + await expect( + client.lset(key, oobIndex, element), + ).rejects.toThrow(RequestError); + + // assert lset result + expect(await client.lset(key, index, element)).toEqual("OK"); + expect(await client.lrange(key, 0, negativeIndex)).toEqual( + expectedList, ); - checkSimple( - await client.invokeScript(script, { - keys: [key1], - args: [Buffer.from("value1")], - }), - ).toEqual("OK"); - /// Reuse the same script with different parameters. - checkSimple( - await client.invokeScript(script, { - keys: [key2], - args: [Buffer.from("value2")], - }), - ).toEqual("OK"); + // assert lset with a negative index for the last element in the list + expect(await client.lset(key, negativeIndex, element)).toEqual( + "OK", + ); + expect(await client.lrange(key, 0, negativeIndex)).toEqual( + expectedList2, + ); - script = new Script( - Buffer.from("return redis.call('GET', KEYS[1])"), + // assert lset against a non-list key + const nonListKey = "nonListKey"; + expect(await client.sadd(nonListKey, ["a"])).toEqual(1); + + await expect(client.lset(nonListKey, 0, "b")).rejects.toThrow( + RequestError, ); - checkSimple( - await client.invokeScript(script, { keys: [key1] }), - ).toEqual("value1"); - checkSimple( - await client.invokeScript(script, { keys: [key2] }), - ).toEqual("value2"); + //test lset for binary key and element values + const key2 = uuidv4(); + expect(await client.lpush(key2, lpushArgs)).toEqual(4); + // assert lset result + expect( + await client.lset(Buffer.from(key2), index, element), + ).toEqual("OK"); + expect(await client.lrange(key2, 0, negativeIndex)).toEqual( + expectedList, + ); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `script test_binary_%p`, + `ltrim with existing key and key that holds a value that is not a list_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = uuidv4(); - const key2 = uuidv4(); - - let script = new Script("return 'Hello'"); - checkSimple(await client.invokeScript(script)).toEqual("Hello"); - - script = new Script( - "return redis.call('SET', KEYS[1], ARGV[1])", - ); - checkSimple( - await client.invokeScript(script, { - keys: [key1], - args: ["value1"], - }), - ).toEqual("OK"); + const key = uuidv4(); + const valueList = ["value4", "value3", "value2", "value1"]; + expect(await client.lpush(key, valueList)).toEqual(4); + expect(await client.ltrim(key, 0, 1)).toEqual("OK"); + expect(await client.lrange(key, 0, -1)).toEqual([ + "value1", + "value2", + ]); - /// Reuse the same script with different parameters. - checkSimple( - await client.invokeScript(script, { - keys: [key2], - args: ["value2"], - }), - ).toEqual("OK"); + /// `start` is greater than `end` so the key will be removed. + expect(await client.ltrim(key, 4, 2)).toEqual("OK"); + expect(await client.lrange(key, 0, -1)).toEqual([]); - script = new Script("return redis.call('GET', KEYS[1])"); - checkSimple( - await client.invokeScript(script, { keys: [key1] }), - ).toEqual("value1"); + expect(await client.set(key, "foo")).toEqual("OK"); - checkSimple( - await client.invokeScript(script, { keys: [key2] }), - ).toEqual("value2"); + try { + expect(await client.ltrim(key, 0, 1)).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value", + ); + } + + //test for binary key as input to the command + const key2 = uuidv4(); + expect(await client.lpush(key2, valueList)).toEqual(4); + expect(await client.ltrim(Buffer.from(key2), 0, 1)).toEqual( + "OK", + ); + expect(await client.lrange(key2, 0, -1)).toEqual([ + "value1", + "value2", + ]); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zadd and zaddIncr test_%p`, + `lrem with existing key and non existing key_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const membersScores = { one: 1, two: 2, three: 3 }; + const key1 = uuidv4(); + const valueList = [ + "value1", + "value2", + "value1", + "value1", + "value2", + ]; + expect(await client.lpush(key1, valueList)).toEqual(5); + expect(await client.lrem(key1, 2, "value1")).toEqual(2); + expect(await client.lrange(key1, 0, -1)).toEqual([ + "value2", + "value2", + "value1", + ]); + expect(await client.lrem(key1, -1, "value2")).toEqual(1); + expect(await client.lrange(key1, 0, -1)).toEqual([ + "value2", + "value1", + ]); + expect(await client.lrem(key1, 0, "value2")).toEqual(1); + expect(await client.lrange(key1, 0, -1)).toEqual(["value1"]); + expect(await client.lrem("nonExistingKey", 2, "value")).toEqual( + 0, + ); - expect(await client.zadd(key, membersScores)).toEqual(3); - expect(await client.zaddIncr(key, "one", 2)).toEqual(3.0); + // test for binary key and element as input to the command + const key2 = uuidv4(); + expect(await client.lpush(key2, valueList)).toEqual(5); + expect( + await client.lrem( + Buffer.from(key2), + 2, + Buffer.from("value1"), + ), + ).toEqual(2); + expect(await client.lrange(key2, 0, -1)).toEqual([ + "value2", + "value2", + "value1", + ]); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zadd and zaddIncr with NX XX test_%p`, + `rpush and rpop with existing and non existing key_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const membersScores = { one: 1, two: 2, three: 3 }; - expect( - await client.zadd(key, membersScores, { - conditionalChange: "onlyIfExists", - }), - ).toEqual(0); - - expect( - await client.zadd(key, membersScores, { - conditionalChange: "onlyIfDoesNotExist", - }), - ).toEqual(3); - - expect( - await client.zaddIncr(key, "one", 5.0, { - conditionalChange: "onlyIfDoesNotExist", - }), - ).toEqual(null); + const key1 = uuidv4(); + const key2 = Buffer.from(uuidv4()); + const valueList1 = ["value1", "value2", "value3", "value4"]; + const valueList2 = ["value5", "value6", "value7"]; + expect(await client.rpush(key1, valueList1)).toEqual(4); + expect(await client.rpop(key1)).toEqual("value4"); + expect(await client.rpopCount(key1, 2)).toEqual([ + "value3", + "value2", + ]); + expect(await client.rpop("nonExistingKey")).toEqual(null); + expect(await client.rpush(key2, valueList2)).toEqual(3); + expect(await client.rpop(key2, Decoder.Bytes)).toEqual( + Buffer.from("value7"), + ); + expect(await client.rpopCount(key2, 2, Decoder.Bytes)).toEqual([ + Buffer.from("value6"), + Buffer.from("value5"), + ]); expect( - await client.zaddIncr(key, "one", 5.0, { - conditionalChange: "onlyIfExists", - }), - ).toEqual(6.0); + await client.rpush(key2, [Buffer.from("value8")]), + ).toEqual(1); + expect(await client.rpop(key2, Decoder.Bytes)).toEqual( + Buffer.from("value8"), + ); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zadd and zaddIncr with GT LT test_%p`, + `rpush and rpop with key that holds a value that is not a list_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { const key = uuidv4(); - const membersScores = { one: -3, two: 2, three: 3 }; - - expect(await client.zadd(key, membersScores)).toEqual(3); - membersScores["one"] = 10; - - expect( - await client.zadd( - key, - membersScores, - { - updateOptions: "scoreGreaterThanCurrent", - }, - true, - ), - ).toEqual(1); - - expect( - await client.zadd( - key, - membersScores, - { - updateOptions: "scoreLessThanCurrent", - }, - true, - ), - ).toEqual(0); + expect(await client.set(key, "foo")).toEqual("OK"); - expect( - await client.zaddIncr(key, "one", -3.0, { - updateOptions: "scoreLessThanCurrent", - }), - ).toEqual(7.0); + try { + expect(await client.rpush(key, ["bar"])).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value", + ); + } - expect( - await client.zaddIncr(key, "one", -3.0, { - updateOptions: "scoreGreaterThanCurrent", - }), - ).toEqual(null); + try { + expect(await client.rpop(key)).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value", + ); + } }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zrem test_%p`, + `rpushx list_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const membersScores = { one: 1, two: 2, three: 3 }; - expect(await client.zadd(key, membersScores)).toEqual(3); - expect(await client.zrem(key, ["one"])).toEqual(1); - expect(await client.zrem(key, ["one", "two", "three"])).toEqual( - 2, + const key1 = uuidv4(); + const key2 = uuidv4(); + const key3 = uuidv4(); + + expect(await client.rpush(key1, ["0"])).toEqual(1); + expect(await client.rpushx(key1, ["1", "2", "3"])).toEqual(4); + expect(await client.lrange(key1, 0, -1)).toEqual([ + "0", + "1", + "2", + "3", + ]); + + expect(await client.rpushx(key2, ["1"])).toEqual(0); + expect(await client.lrange(key2, 0, -1)).toEqual([]); + + // Key exists, but is not a list + expect(await client.set(key3, "bar")); + await expect(client.rpushx(key3, ["_"])).rejects.toThrow( + RequestError, + ); + + // Empty element list + await expect(client.rpushx(key2, [])).rejects.toThrow( + RequestError, ); + + //test for binary key and elemnts as inputs to the command. + const key4 = uuidv4(); + expect(await client.rpush(key4, ["0"])).toEqual(1); expect( - await client.zrem("non_existing_set", ["member"]), - ).toEqual(0); + await client.rpushx(Buffer.from(key4), [ + Buffer.from("1"), + Buffer.from("2"), + Buffer.from("3"), + ]), + ).toEqual(4); + expect(await client.lrange(key4, 0, -1)).toEqual([ + "0", + "1", + "2", + "3", + ]); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zcard test_%p`, + `sadd, srem, scard and smembers with existing set_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { const key = uuidv4(); - const membersScores = { one: 1, two: 2, three: 3 }; - expect(await client.zadd(key, membersScores)).toEqual(3); - expect(await client.zcard(key)).toEqual(3); - expect(await client.zrem(key, ["one"])).toEqual(1); - expect(await client.zcard(key)).toEqual(2); + const keyEncoded = Buffer.from(key); + const valueList = ["member1", "member2", "member3", "member4"]; + expect(await client.sadd(key, valueList)).toEqual(4); + expect( + await client.srem(key, ["member3", "nonExistingMember"]), + ).toEqual(1); + + /// compare the 2 sets. + expect(await client.smembers(key)).toEqual( + new Set(["member1", "member2", "member4"]), + ); + expect(await client.srem(key, ["member1"])).toEqual(1); + expect(await client.scard(key)).toEqual(2); + + // with key and members as buffers + expect( + await client.sadd(keyEncoded, [Buffer.from("member5")]), + ).toEqual(1); + expect( + await client.srem(keyEncoded, [Buffer.from("member2")]), + ).toEqual(1); + expect( + await client.smembers(keyEncoded, { + decoder: Decoder.Bytes, + }), + ).toEqual( + new Set([Buffer.from("member4"), Buffer.from("member5")]), + ); + expect(await client.scard(Buffer.from(key))).toEqual(2); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zintercard test_%p`, + `smove test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - if (await checkIfServerVersionLessThan("7.0.0")) { - return; - } + const key1 = "{key}" + uuidv4(); + const key2 = "{key}" + uuidv4(); + const key3 = "{key}" + uuidv4(); + const string_key = "{key}" + uuidv4(); + const non_existing_key = "{key}" + uuidv4(); - const key1 = `{key}:${uuidv4()}`; - const key2 = `{key}:${uuidv4()}`; - const stringKey = `{key}:${uuidv4()}`; - const nonExistingKey = `{key}:${uuidv4()}`; - const memberScores1 = { one: 1, two: 2, three: 3 }; - const memberScores2 = { two: 2, three: 3, four: 4 }; + expect(await client.sadd(key1, ["1", "2", "3"])).toEqual(3); + expect(await client.sadd(key2, ["2", "3"])).toEqual(2); - expect(await client.zadd(key1, memberScores1)).toEqual(3); - expect(await client.zadd(key2, memberScores2)).toEqual(3); + // move an element, test key as buffer + expect(await client.smove(Buffer.from(key1), key2, "1")); + expect(await client.smembers(key1)).toEqual( + new Set(["2", "3"]), + ); + expect(await client.smembers(key2)).toEqual( + new Set(["1", "2", "3"]), + ); - expect(await client.zintercard([key1, key2])).toEqual(2); - expect(await client.zintercard([key1, nonExistingKey])).toEqual( - 0, + // moved element already exists in the destination set, test member as buffer + expect(await client.smove(key2, key1, Buffer.from("2"))); + expect(await client.smembers(key1)).toEqual( + new Set(["2", "3"]), + ); + expect(await client.smembers(key2)).toEqual( + new Set(["1", "3"]), ); - expect(await client.zintercard([key1, key2], 0)).toEqual(2); - expect(await client.zintercard([key1, key2], 1)).toEqual(1); - expect(await client.zintercard([key1, key2], 2)).toEqual(2); + // attempt to move from a non-existing key + expect(await client.smove(non_existing_key, key1, "4")).toEqual( + false, + ); + expect(await client.smembers(key1)).toEqual( + new Set(["2", "3"]), + ); - // invalid argument - key list must not be empty - await expect(client.zintercard([])).rejects.toThrow(); + // move to a new set + expect(await client.smove(key1, key3, "2")); + expect(await client.smembers(key1)).toEqual(new Set(["3"])); + expect(await client.smembers(key3)).toEqual(new Set(["2"])); - // invalid argument - limit must be non-negative - await expect( - client.zintercard([key1, key2], -1), - ).rejects.toThrow(); + // attempt to move a missing element + expect(await client.smove(key1, key3, "42")).toEqual(false); + expect(await client.smembers(key1)).toEqual(new Set(["3"])); + expect(await client.smembers(key3)).toEqual(new Set(["2"])); - // key exists, but it is not a sorted set - expect(await client.set(stringKey, "foo")).toEqual("OK"); - await expect(client.zintercard([stringKey])).rejects.toThrow(); - }, protocol); - }, + // move missing element to missing key + expect( + await client.smove(key1, non_existing_key, "42"), + ).toEqual(false); + expect(await client.smembers(key1)).toEqual(new Set(["3"])); + expect(await client.type(non_existing_key)).toEqual("none"); + + // key exists, but it is not a set + expect(await client.set(string_key, "value")).toEqual("OK"); + await expect( + client.smove(string_key, key1, "_"), + ).rejects.toThrow(); + }, protocol); + }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zscore test_%p`, + `srem, scard and smembers with non existing key_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = uuidv4(); - const key2 = uuidv4(); - const membersScores = { one: 1, two: 2, three: 3 }; - expect(await client.zadd(key1, membersScores)).toEqual(3); - expect(await client.zscore(key1, "one")).toEqual(1.0); - expect(await client.zscore(key1, "nonExistingMember")).toEqual( - null, + expect(await client.srem("nonExistingKey", ["member"])).toEqual( + 0, + ); + expect(await client.scard("nonExistingKey")).toEqual(0); + expect(await client.smembers("nonExistingKey")).toEqual( + new Set(), ); - expect( - await client.zscore("nonExistingKey", "nonExistingMember"), - ).toEqual(null); - - checkSimple(await client.set(key2, "foo")).toEqual("OK"); - await expect(client.zscore(key2, "foo")).rejects.toThrow(); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zcount test_%p`, + `sadd, srem, scard and smembers with with key that holds a value that is not a set_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = uuidv4(); - const key2 = uuidv4(); - const membersScores = { one: 1, two: 2, three: 3 }; - expect(await client.zadd(key1, membersScores)).toEqual(3); - expect( - await client.zcount( - key1, - "negativeInfinity", - "positiveInfinity", - ), - ).toEqual(3); - expect( - await client.zcount( - key1, - { value: 1, isInclusive: false }, - { value: 3, isInclusive: false }, - ), - ).toEqual(1); - expect( - await client.zcount( - key1, - { value: 1, isInclusive: false }, - { value: 3 }, - ), - ).toEqual(2); - expect( - await client.zcount(key1, "negativeInfinity", { - value: 3, - }), - ).toEqual(3); - expect( - await client.zcount(key1, "positiveInfinity", { - value: 3, - }), - ).toEqual(0); - expect( - await client.zcount( - "nonExistingKey", - "negativeInfinity", - "positiveInfinity", - ), - ).toEqual(0); + const key = uuidv4(); + expect(await client.set(key, "foo")).toEqual("OK"); - checkSimple(await client.set(key2, "foo")).toEqual("OK"); - await expect( - client.zcount(key2, "negativeInfinity", "positiveInfinity"), - ).rejects.toThrow(); + try { + expect(await client.sadd(key, ["bar"])).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value", + ); + } + + try { + expect(await client.srem(key, ["bar"])).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value", + ); + } + + try { + expect(await client.scard(key)).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value", + ); + } + + try { + expect(await client.smembers(key)).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value", + ); + } }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zrange by index test_%p`, + `sinter test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const membersScores = { one: 1, two: 2, three: 3 }; - expect(await client.zadd(key, membersScores)).toEqual(3); + const key1 = `{key}-1-${uuidv4()}`; + const key2 = `{key}-2-${uuidv4()}`; + const non_existing_key = `{key}`; + const member1_list = ["a", "b", "c", "d"]; + const member2_list = ["c", "d", "e"]; - checkSimple( - await client.zrange(key, { start: 0, stop: 1 }), - ).toEqual(["one", "two"]); - const result = await client.zrangeWithScores(key, { - start: 0, - stop: -1, - }); + // positive test case + expect(await client.sadd(key1, member1_list)).toEqual(4); + expect(await client.sadd(key2, member2_list)).toEqual(3); + expect(await client.sinter([key1, key2])).toEqual( + new Set(["c", "d"]), + ); + // positive test case with keys and return value as buffers expect( - compareMaps(result, { - one: 1.0, - two: 2.0, - three: 3.0, - }), - ).toBe(true); - checkSimple( - await client.zrange(key, { start: 0, stop: 1 }, true), - ).toEqual(["three", "two"]); - expect(await client.zrange(key, { start: 3, stop: 1 })).toEqual( - [], + await client.sinter( + [Buffer.from(key1), Buffer.from(key2)], + { decoder: Decoder.Bytes }, + ), + ).toEqual(new Set([Buffer.from("c"), Buffer.from("d")])); + + // invalid argument - key list must not be empty + try { + expect(await client.sinter([])).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "ResponseError: wrong number of arguments", + ); + } + + // non-existing key returns empty set + expect(await client.sinter([key1, non_existing_key])).toEqual( + new Set(), ); - expect( - await client.zrangeWithScores(key, { start: 3, stop: 1 }), - ).toEqual({}); + + // non-set key + expect(await client.set(key2, "value")).toEqual("OK"); + + try { + expect(await client.sinter([key2])).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "Operation against a key holding the wrong kind of value", + ); + } }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zrange by score test_%p`, + `sintercard test_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const membersScores = { one: 1, two: 2, three: 3 }; - expect(await client.zadd(key, membersScores)).toEqual(3); + await runTest(async (client: BaseClient, cluster) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) { + return; + } - checkSimple( - await client.zrange(key, { - start: "negativeInfinity", - stop: { value: 3, isInclusive: false }, - type: "byScore", - }), - ).toEqual(["one", "two"]); - const result = await client.zrangeWithScores(key, { - start: "negativeInfinity", - stop: "positiveInfinity", - type: "byScore", - }); + const key1 = `{key}-${uuidv4()}`; + const key2 = `{key}-${uuidv4()}`; + const nonExistingKey = `{key}-${uuidv4()}`; + const stringKey = `{key}-${uuidv4()}`; + const member1_list = ["a", "b", "c", "d"]; + const member2_list = ["b", "c", "d", "e"]; - expect( - compareMaps(result, { - one: 1.0, - two: 2.0, - three: 3.0, - }), - ).toBe(true); - checkSimple( - await client.zrange( - key, - { - start: { value: 3, isInclusive: false }, - stop: "negativeInfinity", - type: "byScore", - }, - true, - ), - ).toEqual(["two", "one"]); + expect(await client.sadd(key1, member1_list)).toEqual(4); + expect(await client.sadd(key2, member2_list)).toEqual(4); - checkSimple( - await client.zrange(key, { - start: "negativeInfinity", - stop: "positiveInfinity", - limit: { offset: 1, count: 2 }, - type: "byScore", - }), - ).toEqual(["two", "three"]); + expect(await client.sintercard([key1, key2])).toEqual(3); - expect( - await client.zrange( - key, - { - start: "negativeInfinity", - stop: { value: 3, isInclusive: false }, - type: "byScore", - }, - true, - ), - ).toEqual([]); + // returns limit as cardinality when the limit is reached partway through the computation + const limit = 2; + expect(await client.sintercard([key1, key2], limit)).toEqual( + limit, + ); - expect( - await client.zrange(key, { - start: "positiveInfinity", - stop: { value: 3, isInclusive: false }, - type: "byScore", - }), - ).toEqual([]); + // returns actual cardinality if limit is higher + expect(await client.sintercard([key1, key2], 4)).toEqual(3); + + // one of the keys is empty, intersection is empty, cardinality equals 0 + expect(await client.sintercard([key1, nonExistingKey])).toEqual( + 0, + ); expect( - await client.zrangeWithScores( - key, - { - start: "negativeInfinity", - stop: { value: 3, isInclusive: false }, - type: "byScore", - }, - true, + await client.sintercard([nonExistingKey, nonExistingKey]), + ).toEqual(0); + expect( + await client.sintercard( + [nonExistingKey, nonExistingKey], + 2, ), - ).toEqual({}); + ).toEqual(0); + // with keys as binary buffers expect( - await client.zrangeWithScores(key, { - start: "positiveInfinity", - stop: { value: 3, isInclusive: false }, - type: "byScore", - }), - ).toEqual({}); + await client.sintercard([ + Buffer.from(key1), + Buffer.from(key2), + ]), + ).toEqual(3); + + // invalid argument - key list must not be empty + await expect(client.sintercard([])).rejects.toThrow( + RequestError, + ); + + // source key exists, but it is not a set + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.sintercard([key1, stringKey]), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zrange by lex test_%p`, + `sinterstore test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const membersScores = { a: 1, b: 2, c: 3 }; - expect(await client.zadd(key, membersScores)).toEqual(3); - - checkSimple( - await client.zrange(key, { - start: "negativeInfinity", - stop: { value: "c", isInclusive: false }, - type: "byLex", - }), - ).toEqual(["a", "b"]); + const key1 = `{key}-1-${uuidv4()}`; + const key2 = `{key}-2-${uuidv4()}`; + const key3 = `{key}-3-${uuidv4()}`; + const nonExistingKey = `{key}-4-${uuidv4()}`; + const stringKey = `{key}-5-${uuidv4()}`; + const member1_list = ["a", "b", "c"]; + const member2_list = ["c", "d", "e"]; - checkSimple( - await client.zrange(key, { - start: "negativeInfinity", - stop: "positiveInfinity", - limit: { offset: 1, count: 2 }, - type: "byLex", - }), - ).toEqual(["b", "c"]); + expect(await client.sadd(key1, member1_list)).toEqual(3); + expect(await client.sadd(key2, member2_list)).toEqual(3); - checkSimple( - await client.zrange( - key, - { - start: { value: "c", isInclusive: false }, - stop: "negativeInfinity", - type: "byLex", - }, - true, - ), - ).toEqual(["b", "a"]); + // store in a new key + expect(await client.sinterstore(key3, [key1, key2])).toEqual(1); + expect(await client.smembers(key3)).toEqual(new Set(["c"])); + + // overwrite existing set, which is also a source set + expect(await client.sinterstore(key2, [key2, key3])).toEqual(1); + expect(await client.smembers(key2)).toEqual(new Set(["c"])); + + // source set is the same as the existing set + expect(await client.sinterstore(key2, [key2])).toEqual(1); + expect(await client.smembers(key2)).toEqual(new Set(["c"])); + // intersection with non-existing key expect( - await client.zrange( - key, - { - start: "negativeInfinity", - stop: { value: "c", isInclusive: false }, - type: "byLex", - }, - true, - ), - ).toEqual([]); + await client.sinterstore(key1, [key2, nonExistingKey]), + ).toEqual(0); + expect(await client.smembers(key1)).toEqual(new Set()); + + // invalid argument - key list must not be empty + await expect(client.sinterstore(key3, [])).rejects.toThrow(); + + // non-set key + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.sinterstore(key3, [stringKey]), + ).rejects.toThrow(); + + // overwrite non-set key + expect(await client.sinterstore(stringKey, [key2])).toEqual(1); + expect(await client.smembers(stringKey)).toEqual(new Set("c")); + // with destination and keys as binary buffers + expect(await client.sadd(key1, ["a", "b", "c"])); + expect(await client.sadd(key2, ["c", "d", "e"])); expect( - await client.zrange(key, { - start: "positiveInfinity", - stop: { value: "c", isInclusive: false }, - type: "byLex", - }), - ).toEqual([]); + await client.sinterstore(Buffer.from(key3), [ + Buffer.from(key1), + Buffer.from(key2), + ]), + ).toEqual(1); + expect(await client.smembers(key3)).toEqual(new Set(["c"])); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zrange different typesn of keys test_%p`, + `sdiff test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); + const key1 = `{key}-1-${uuidv4()}`; + const key2 = `{key}-2-${uuidv4()}`; + const stringKey = `{key}-3-${uuidv4()}`; + const nonExistingKey = `{key}-4-${uuidv4()}`; + const member1_list = ["a", "b", "c"]; + const member2_list = ["c", "d", "e"]; + + expect(await client.sadd(key1, member1_list)).toEqual(3); + expect(await client.sadd(key2, member2_list)).toEqual(3); + + expect(await client.sdiff([key1, key2])).toEqual( + new Set(["a", "b"]), + ); + expect(await client.sdiff([key2, key1])).toEqual( + new Set(["d", "e"]), + ); + + expect(await client.sdiff([key1, nonExistingKey])).toEqual( + new Set(["a", "b", "c"]), + ); + expect(await client.sdiff([nonExistingKey, key1])).toEqual( + new Set(), + ); + + // key and return value as binary buffers expect( - await client.zrange("nonExistingKey", { - start: 0, - stop: 1, + await client.sdiff([Buffer.from(key1), Buffer.from(key2)], { + decoder: Decoder.Bytes, }), - ).toEqual([]); - + ).toEqual(new Set([Buffer.from("a"), Buffer.from("b")])); expect( - await client.zrangeWithScores("nonExistingKey", { - start: 0, - stop: 1, + await client.sdiff([Buffer.from(key2), Buffer.from(key1)], { + decoder: Decoder.Bytes, }), - ).toEqual({}); + ).toEqual(new Set([Buffer.from("d"), Buffer.from("e")])); - expect(await client.set(key, "value")).toEqual("OK"); + // invalid arg - key list must not be empty + await expect(client.sdiff([])).rejects.toThrow(); - await expect( - client.zrange(key, { start: 0, stop: 1 }), - ).rejects.toThrow(); + // key exists, but it is not a set + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect(client.sdiff([stringKey])).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `sdiffstore test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = `{key}-1-${uuidv4()}`; + const key2 = `{key}-2-${uuidv4()}`; + const key3 = `{key}-3-${uuidv4()}`; + const stringKey = `{key}-4-${uuidv4()}`; + const nonExistingKey = `{key}-5-${uuidv4()}`; + const member1_list = ["a", "b", "c"]; + const member2_list = ["c", "d", "e"]; + + expect(await client.sadd(key1, member1_list)).toEqual(3); + expect(await client.sadd(key2, member2_list)).toEqual(3); + + // store diff in new key + expect(await client.sdiffstore(key3, [key1, key2])).toEqual(2); + expect(await client.smembers(key3)).toEqual( + new Set(["a", "b"]), + ); + + // overwrite existing set + expect(await client.sdiffstore(key3, [key2, key1])).toEqual(2); + expect(await client.smembers(key3)).toEqual( + new Set(["d", "e"]), + ); + + // overwrite one of the source sets + expect(await client.sdiffstore(key3, [key2, key3])).toEqual(1); + expect(await client.smembers(key3)).toEqual(new Set(["c"])); + + // diff between non-empty set and empty set + expect( + await client.sdiffstore(key3, [key1, nonExistingKey]), + ).toEqual(3); + expect(await client.smembers(key3)).toEqual( + new Set(["a", "b", "c"]), + ); + + // diff between empty set and non-empty set + expect( + await client.sdiffstore(key3, [nonExistingKey, key1]), + ).toEqual(0); + expect(await client.smembers(key3)).toEqual(new Set()); + + // invalid argument - key list must not be empty + await expect(client.sdiffstore(key3, [])).rejects.toThrow(); + // source key exists, but it is not a set + expect(await client.set(stringKey, "foo")).toEqual("OK"); await expect( - client.zrangeWithScores(key, { start: 0, stop: 1 }), + client.sdiffstore(key3, [stringKey]), ).rejects.toThrow(); + + // overwrite a key holding a non-set value + expect( + await client.sdiffstore(stringKey, [key1, key2]), + ).toEqual(2); + expect(await client.smembers(stringKey)).toEqual( + new Set(["a", "b"]), + ); + + // with destination and keys as binary buffers + expect( + await client.sdiffstore(Buffer.from(key3), [ + Buffer.from(key1), + Buffer.from(key2), + ]), + ).toEqual(2); + expect(await client.smembers(key3)).toEqual( + new Set(["a", "b"]), + ); }, protocol); }, config.timeout, ); - // Zinterstore command tests - async function zinterstoreWithAggregation(client: BaseClient) { - const key1 = "{testKey}:1-" + uuidv4(); - const key2 = "{testKey}:2-" + uuidv4(); - const key3 = "{testKey}:3-" + uuidv4(); - const range = { - start: 0, - stop: -1, - }; + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `sscan test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = "{key}-1" + uuidv4(); + const initialCursor = "0"; + const defaultCount = 10; - const membersScores1 = { one: 1.0, two: 2.0 }; - const membersScores2 = { one: 2.0, two: 3.0, three: 4.0 }; + const numberMembers: string[] = []; - expect(await client.zadd(key1, membersScores1)).toEqual(2); - expect(await client.zadd(key2, membersScores2)).toEqual(3); + for (let i = 0; i < 50000; i++) { + numberMembers[i] = i.toString(); + } - // Intersection results are aggregated by the MAX score of elements - expect(await client.zinterstore(key3, [key1, key2], "MAX")).toEqual(2); - const zinterstoreMapMax = await client.zrangeWithScores(key3, range); - const expectedMapMax = { - one: 2, - two: 3, - }; - expect(compareMaps(zinterstoreMapMax, expectedMapMax)).toBe(true); + const numberMembersSet: string[] = numberMembers; + const charMembers: string[] = ["a", "b", "c", "d", "e"]; + const charMembersSet: Set = new Set(charMembers); + const resultCursorIndex = 0; + const resultCollectionIndex = 1; - // Intersection results are aggregated by the MIN score of elements - expect(await client.zinterstore(key3, [key1, key2], "MIN")).toEqual(2); - const zinterstoreMapMin = await client.zrangeWithScores(key3, range); - const expectedMapMin = { - one: 1, - two: 2, - }; - expect(compareMaps(zinterstoreMapMin, expectedMapMin)).toBe(true); + // Result contains the whole set + expect(await client.sadd(key1, charMembers)).toEqual( + charMembers.length, + ); + let result = await client.sscan(key1, initialCursor); + expect(await result[resultCursorIndex]).toEqual(initialCursor); + expect(result[resultCollectionIndex].length).toEqual( + charMembers.length, + ); - // Intersection results are aggregated by the SUM score of elements - expect(await client.zinterstore(key3, [key1, key2], "SUM")).toEqual(2); - const zinterstoreMapSum = await client.zrangeWithScores(key3, range); - const expectedMapSum = { - one: 3, - two: 5, - }; - expect(compareMaps(zinterstoreMapSum, expectedMapSum)).toBe(true); - } + const resultMembers = result[resultCollectionIndex] as string[]; - async function zinterstoreBasicTest(client: BaseClient) { - const key1 = "{testKey}:1-" + uuidv4(); - const key2 = "{testKey}:2-" + uuidv4(); - const key3 = "{testKey}:3-" + uuidv4(); - const range = { - start: 0, - stop: -1, - }; + const allResultMember = resultMembers.every((member) => + charMembersSet.has(member), + ); + expect(allResultMember).toEqual(true); - const membersScores1 = { one: 1.0, two: 2.0 }; - const membersScores2 = { one: 2.0, two: 3.0, three: 4.0 }; + // Test with key, cursor, result value as binary buffers + const encodedResult = await client.sscan( + Buffer.from(key1), + Buffer.from(initialCursor), + { decoder: Decoder.Bytes }, + ); + const encodedResultMembers = encodedResult[ + resultCollectionIndex + ] as GlideString[]; + const allEncodedResultMembers = encodedResultMembers.every( + (member) => charMembersSet.has(member.toString()), + ); + expect(allEncodedResultMembers).toEqual(true); - expect(await client.zadd(key1, membersScores1)).toEqual(2); - expect(await client.zadd(key2, membersScores2)).toEqual(3); + // Testing sscan with match + result = await client.sscan(key1, initialCursor, { + match: "a", + }); + expect(result[resultCursorIndex]).toEqual(initialCursor); + expect(result[resultCollectionIndex]).toEqual(["a"]); - expect(await client.zinterstore(key3, [key1, key2])).toEqual(2); - const zinterstoreMap = await client.zrangeWithScores(key3, range); - const expectedMap = { - one: 3, - two: 5, - }; - expect(compareMaps(zinterstoreMap, expectedMap)).toBe(true); - } + // Result contains a subset of the key + expect(await client.sadd(key1, numberMembers)).toEqual( + numberMembers.length, + ); - async function zinterstoreWithWeightsAndAggregation(client: BaseClient) { - const key1 = "{testKey}:1-" + uuidv4(); - const key2 = "{testKey}:2-" + uuidv4(); - const key3 = "{testKey}:3-" + uuidv4(); - const range = { - start: 0, - stop: -1, - }; - const membersScores1 = { one: 1.0, two: 2.0 }; - const membersScores2 = { one: 2.0, two: 3.0, three: 4.0 }; + let resultCursor = "0"; + let secondResultValues: GlideString[] = []; - expect(await client.zadd(key1, membersScores1)).toEqual(2); - expect(await client.zadd(key2, membersScores2)).toEqual(3); + let isFirstLoop = true; - // Scores are multiplied by 2.0 for key1 and key2 during aggregation. - expect( - await client.zinterstore( - key3, - [ - [key1, 2.0], - [key2, 2.0], - ], - "SUM", - ), - ).toEqual(2); - const zinterstoreMapMultiplied = await client.zrangeWithScores( - key3, - range, - ); - const expectedMapMultiplied = { - one: 6, - two: 10, - }; - expect( - compareMaps(zinterstoreMapMultiplied, expectedMapMultiplied), - ).toBe(true); - } + do { + result = await client.sscan(key1, resultCursor); + resultCursor = result[resultCursorIndex].toString(); + secondResultValues = result[resultCollectionIndex]; - async function zinterstoreEmptyCases(client: BaseClient) { - const key1 = "{testKey}:1-" + uuidv4(); - const key2 = "{testKey}:2-" + uuidv4(); + if (isFirstLoop) { + expect(resultCursor).not.toBe("0"); + isFirstLoop = false; + } else if (resultCursor === initialCursor) { + break; + } - // Non existing key - expect( - await client.zinterstore(key2, [ - key1, - "{testKey}-non_existing_key", - ]), - ).toEqual(0); + // Scan with result cursor has a different set + const secondResult = await client.sscan(key1, resultCursor); + const newResultCursor = + secondResult[resultCursorIndex].toString(); + expect(resultCursor).not.toBe(newResultCursor); + resultCursor = newResultCursor; + expect(result[resultCollectionIndex]).not.toBe( + secondResult[resultCollectionIndex], + ); + secondResultValues = secondResult[resultCollectionIndex]; + } while (resultCursor != initialCursor); // 0 is returned for the cursor of the last iteration. - // Empty list check - await expect(client.zinterstore("{xyz}", [])).rejects.toThrow(); - } + const allSecondResultValues = Object.keys( + secondResultValues, + ).every((value) => value in numberMembersSet); + expect(allSecondResultValues).toEqual(true); - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zinterstore test_%p`, - async (protocol) => { - await runTest(async (client: BaseClient) => { - await zinterstoreBasicTest(client); - await zinterstoreWithAggregation(client); - await zinterstoreWithWeightsAndAggregation(client); - await zinterstoreEmptyCases(client); + // Test match pattern + result = await client.sscan(key1, initialCursor, { + match: "*", + }); + expect(result[resultCursorIndex]).not.toEqual(initialCursor); + expect( + result[resultCollectionIndex].length, + ).toBeGreaterThanOrEqual(defaultCount); + + // Test count + result = await client.sscan(key1, initialCursor, { count: 20 }); + expect(result[resultCursorIndex]).not.toEqual(0); + expect( + result[resultCollectionIndex].length, + ).toBeGreaterThanOrEqual(20); + + // Test count with match returns a non-empty list + result = await client.sscan(key1, initialCursor, { + match: "1*", + count: 30, + }); + expect(result[resultCursorIndex]).not.toEqual(initialCursor); + expect( + result[resultCollectionIndex].length, + ).toBeGreaterThanOrEqual(0); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `type test_%p`, + `sunion test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - checkSimple(await client.set(key, "value")).toEqual("OK"); - checkSimple(await client.type(key)).toEqual("string"); - checkSimple(await client.del([key])).toEqual(1); + const key1 = `{key}:${uuidv4()}`; + const key2 = `{key}:${uuidv4()}`; + const stringKey = `{key}:${uuidv4()}`; + const nonExistingKey = `{key}:${uuidv4()}`; + const memberList1 = ["a", "b", "c"]; + const memberList2 = ["b", "c", "d", "e"]; - checkSimple(await client.lpush(key, ["value"])).toEqual(1); - checkSimple(await client.type(key)).toEqual("list"); - checkSimple(await client.del([key])).toEqual(1); + expect(await client.sadd(key1, memberList1)).toEqual(3); + expect(await client.sadd(key2, memberList2)).toEqual(4); + expect(await client.sunion([key1, key2])).toEqual( + new Set(["a", "b", "c", "d", "e"]), + ); - checkSimple(await client.sadd(key, ["value"])).toEqual(1); - checkSimple(await client.type(key)).toEqual("set"); - checkSimple(await client.del([key])).toEqual(1); + // with return value as binary buffers + expect( + await client.sunion([key1, Buffer.from(key2)], { + decoder: Decoder.Bytes, + }), + ).toEqual( + new Set( + ["a", "b", "c", "d", "e"].map((member) => + Buffer.from(member), + ), + ), + ); - checkSimple(await client.zadd(key, { member: 1.0 })).toEqual(1); - checkSimple(await client.type(key)).toEqual("zset"); - checkSimple(await client.del([key])).toEqual(1); + // invalid argument - key list must not be empty + await expect(client.sunion([])).rejects.toThrow(); - checkSimple(await client.hset(key, { field: "value" })).toEqual( - 1, + // non-existing key returns the set of existing keys + expect(await client.sunion([key1, nonExistingKey])).toEqual( + new Set(memberList1), ); - checkSimple(await client.type(key)).toEqual("hash"); - checkSimple(await client.del([key])).toEqual(1); - - await client.customCommand([ - "XADD", - key, - "*", - "field", - "value", - ]); - checkSimple(await client.type(key)).toEqual("stream"); - checkSimple(await client.del([key])).toEqual(1); - checkSimple(await client.type(key)).toEqual("none"); - }, protocol); - }, - config.timeout, - ); - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `echo test_%p`, - async (protocol) => { - await runTest(async (client: BaseClient) => { - const message = uuidv4(); - checkSimple(await client.echo(message)).toEqual(message); + // key exists, but it is not a set + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect(client.sunion([stringKey])).rejects.toThrow(); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `strlen test_%p`, + `sunionstore test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = uuidv4(); - const key1Value = uuidv4(); - const key1ValueLength = key1Value.length; - checkSimple(await client.set(key1, key1Value)).toEqual("OK"); - checkSimple(await client.strlen(key1)).toEqual(key1ValueLength); + const key1 = `{key}:${uuidv4()}`; + const key2 = `{key}:${uuidv4()}`; + const key3 = `{key}:${uuidv4()}`; + const key4 = `{key}:${uuidv4()}`; + const stringKey = `{key}:${uuidv4()}`; + const nonExistingKey = `{key}:${uuidv4()}`; - expect(await client.strlen("nonExistKey")).toEqual(0); + expect(await client.sadd(key1, ["a", "b", "c"])).toEqual(3); + expect(await client.sadd(key2, ["c", "d", "e"])).toEqual(3); + expect(await client.sadd(key3, ["e", "f", "g"])).toEqual(3); - const listName = "myList"; - const listKey1Value = uuidv4(); - const listKey2Value = uuidv4(); + // store union in new key + expect(await client.sunionstore(key4, [key1, key2])).toEqual(5); + expect(await client.smembers(key4)).toEqual( + new Set(["a", "b", "c", "d", "e"]), + ); + // overwrite existing set, test with binary option expect( - await client.lpush(listName, [ - listKey1Value, - listKey2Value, + await client.sunionstore(Buffer.from(key1), [ + Buffer.from(key4), + Buffer.from(key2), ]), - ).toEqual(2); - // An error is returned when key holds a non-string value - await expect(client.strlen(listName)).rejects.toThrow(); - }, protocol); - }, - config.timeout, - ); + ).toEqual(5); + expect(await client.smembers(key1)).toEqual( + new Set(["a", "b", "c", "d", "e"]), + ); - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `lindex test_%p`, - async (protocol) => { - await runTest(async (client: BaseClient) => { - const listName = uuidv4(); - const listKey1Value = uuidv4(); - const listKey2Value = uuidv4(); - expect( - await client.lpush(listName, [ - listKey1Value, - listKey2Value, - ]), - ).toEqual(2); - checkSimple(await client.lindex(listName, 0)).toEqual( - listKey2Value, + // overwrite one of the source keys + expect(await client.sunionstore(key2, [key4, key2])).toEqual(5); + expect(await client.smembers(key2)).toEqual( + new Set(["a", "b", "c", "d", "e"]), ); - checkSimple(await client.lindex(listName, 1)).toEqual( - listKey1Value, + + // union with a non-existing key + expect( + await client.sunionstore(key2, [nonExistingKey]), + ).toEqual(0); + expect(await client.smembers(key2)).toEqual(new Set()); + + // invalid argument - key list must not be empty + await expect(client.sunionstore(key4, [])).rejects.toThrow(); + + // key exists, but it is not a set + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.sunionstore(key4, [stringKey, key1]), + ).rejects.toThrow(); + + // overwrite destination when destination is not a set + expect( + await client.sunionstore(stringKey, [key1, key3]), + ).toEqual(7); + expect(await client.smembers(stringKey)).toEqual( + new Set(["a", "b", "c", "d", "e", "f", "g"]), ); - expect(await client.lindex("notExsitingList", 1)).toEqual(null); - expect(await client.lindex(listName, 3)).toEqual(null); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `linsert test_%p`, + `sismember test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { const key1 = uuidv4(); - const stringKey = uuidv4(); - const nonExistingKey = uuidv4(); - - expect(await client.lpush(key1, ["4", "3", "2", "1"])).toEqual( - 4, - ); - expect( - await client.linsert( - key1, - InsertPosition.Before, - "2", - "1.5", - ), - ).toEqual(5); + const key2 = uuidv4(); + expect(await client.sadd(key1, ["member1"])).toEqual(1); + expect(await client.sismember(key1, "member1")).toEqual(true); expect( - await client.linsert( - key1, - InsertPosition.After, - "3", - "3.5", + await client.sismember( + Buffer.from(key1), + Buffer.from("member1"), ), - ).toEqual(6); - checkSimple(await client.lrange(key1, 0, -1)).toEqual([ - "1", - "1.5", - "2", - "3", - "3.5", - "4", - ]); - + ).toEqual(true); expect( - await client.linsert( - key1, - InsertPosition.Before, - "nonExistingPivot", - "4", - ), - ).toEqual(-1); + await client.sismember(key1, "nonExistingMember"), + ).toEqual(false); expect( - await client.linsert( - nonExistingKey, - InsertPosition.Before, - "pivot", - "elem", - ), - ).toEqual(0); + await client.sismember("nonExistingKey", "member1"), + ).toEqual(false); - // key exists, but it is not a list - expect(await client.set(stringKey, "value")).toEqual("OK"); + expect(await client.set(key2, "foo")).toEqual("OK"); await expect( - client.linsert(stringKey, InsertPosition.Before, "a", "b"), + client.sismember(key2, "member1"), ).rejects.toThrow(); }, protocol); }, @@ -2612,173 +3505,310 @@ export function runBaseTests(config: { ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zpopmin test_%p`, + `smismember test_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { + await runTest(async (client: BaseClient, cluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) { + return; + } + const key = uuidv4(); - const membersScores = { a: 1, b: 2, c: 3 }; - expect(await client.zadd(key, membersScores)).toEqual(3); - expect(await client.zpopmin(key)).toEqual({ a: 1.0 }); + const stringKey = uuidv4(); + const nonExistingKey = uuidv4(); + expect(await client.sadd(key, ["a", "b"])).toEqual(2); expect( - compareMaps(await client.zpopmin(key, 3), { - b: 2.0, - c: 3.0, - }), - ).toBe(true); - expect(await client.zpopmin(key)).toEqual({}); - checkSimple(await client.set(key, "value")).toEqual("OK"); - await expect(client.zpopmin(key)).rejects.toThrow(); - expect(await client.zpopmin("notExsitingKey")).toEqual({}); + await client.smismember(Buffer.from(key), [ + Buffer.from("b"), + "c", + ]), + ).toEqual([true, false]); + + expect(await client.smismember(nonExistingKey, ["b"])).toEqual([ + false, + ]); + + // invalid argument - member list must not be empty + await expect(client.smismember(key, [])).rejects.toThrow( + RequestError, + ); + + // key exists, but it is not a set + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.smismember(stringKey, ["a"]), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zpopmax test_%p`, + `spop and spopCount test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const membersScores = { a: 1, b: 2, c: 3 }; - expect(await client.zadd(key, membersScores)).toEqual(3); - expect(await client.zpopmax(key)).toEqual({ c: 3.0 }); + const key1 = uuidv4(); + const key2 = uuidv4(); + const key2Encoded = Buffer.from(key2); + let members = ["member1", "member2", "member3"]; + let members2 = ["member1", "member2", "member3"]; - expect( - compareMaps(await client.zpopmax(key, 3), { - b: 2.0, - a: 1.0, - }), - ).toBe(true); - expect(await client.zpopmax(key)).toEqual({}); - checkSimple(await client.set(key, "value")).toEqual("OK"); - await expect(client.zpopmax(key)).rejects.toThrow(); - expect(await client.zpopmax("notExsitingKey")).toEqual({}); + expect(await client.sadd(key1, members)).toEqual(3); + expect(await client.sadd(key2, members2)).toEqual(3); + + const result1 = await client.spop(key1); + expect(members).toContain(result1); + + members = members.filter((item) => item != result1); + const result2 = await client.spopCount(key1, 2); + expect(result2).toEqual(new Set(members)); + expect(await client.spop("nonExistingKey")).toEqual(null); + expect(await client.spopCount("nonExistingKey", 1)).toEqual( + new Set(), + ); + + // with keys and return values as buffers + const result3 = await client.spop(key2Encoded, { + decoder: Decoder.Bytes, + }); + expect(members2).toContain(result3?.toString()); + + members2 = members2.filter( + (item) => item != result3?.toString(), + ); + const result4 = await client.spopCount(key2Encoded, 2, { + decoder: Decoder.Bytes, + }); + expect(result4).toEqual( + new Set(members2.map((item) => Buffer.from(item))), + ); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `Pttl test_%p`, + `srandmember and srandmemberCount test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { const key = uuidv4(); - expect(await client.pttl(key)).toEqual(-2); + const members = ["member1", "member2", "member3"]; + expect(await client.sadd(key, members)).toEqual(3); - checkSimple(await client.set(key, "value")).toEqual("OK"); - expect(await client.pttl(key)).toEqual(-1); + const result2 = await client.srandmember(key); + expect(members).toContain(result2); + expect(await client.srandmember("nonExistingKey")).toEqual( + null, + ); - expect(await client.expire(key, 10)).toEqual(true); - let result = await client.pttl(key); - expect(result).toBeGreaterThan(0); - expect(result).toBeLessThanOrEqual(10000); + // with key and return value as buffers + const result3 = await client.srandmember(Buffer.from(key), { + decoder: Decoder.Bytes, + }); + expect(members).toContain(result3?.toString()); - expect( - await client.expireAt( - key, - Math.floor(Date.now() / 1000) + 20, - ), - ).toEqual(true); - result = await client.pttl(key); - expect(result).toBeGreaterThan(0); - expect(result).toBeLessThanOrEqual(20000); + // unique values are expected as count is positive + let result = await client.srandmemberCount(key, 4); + expect(result.length).toEqual(3); + expect(new Set(result)).toEqual(new Set(members)); - expect(await client.pexpireAt(key, Date.now() + 30000)).toEqual( - true, + // with key and return value as buffers + result = await client.srandmemberCount(Buffer.from(key), 4, { + decoder: Decoder.Bytes, + }); + expect(new Set(result)).toEqual( + new Set(members.map((member) => Buffer.from(member))), ); - result = await client.pttl(key); - expect(result).toBeGreaterThan(0); - expect(result).toBeLessThanOrEqual(30000); + + // duplicate values are expected as count is negative + result = await client.srandmemberCount(key, -4); + expect(result.length).toEqual(4); + result.forEach((member) => { + expect(members).toContain(member); + }); + + // empty return values for non-existing or empty keys + result = await client.srandmemberCount(key, 0); + expect(result.length).toEqual(0); + expect(result).toEqual([]); + expect( + await client.srandmemberCount("nonExistingKey", 0), + ).toEqual([]); + + expect(await client.set(key, "value")).toBe("OK"); + await expect(client.srandmember(key)).rejects.toThrow(); + await expect(client.srandmemberCount(key, 2)).rejects.toThrow(); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zremRangeByRank test_%p`, + `exists with existing keys, an non existing key_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const membersScores = { one: 1, two: 2, three: 3 }; - expect(await client.zadd(key, membersScores)).toEqual(3); - expect(await client.zremRangeByRank(key, 2, 1)).toEqual(0); - expect(await client.zremRangeByRank(key, 0, 1)).toEqual(2); - expect(await client.zremRangeByRank(key, 0, 10)).toEqual(1); + const key1 = uuidv4(); + const key2 = uuidv4(); + const value = uuidv4(); + expect(await client.set(key1, value)).toEqual("OK"); + expect(await client.exists([key1])).toEqual(1); + expect(await client.set(key2, value)).toEqual("OK"); expect( - await client.zremRangeByRank("nonExistingKey", 0, -1), - ).toEqual(0); + await client.exists([ + key1, + "nonExistingKey", + Buffer.from(key2), + ]), + ).toEqual(2); + expect(await client.exists([key1, key1])).toEqual(2); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zrank test_%p`, + `unlink multiple existing keys and an non existing key_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = uuidv4(); - const key2 = uuidv4(); - const membersScores = { one: 1.5, two: 2, three: 3 }; - expect(await client.zadd(key1, membersScores)).toEqual(3); - expect(await client.zrank(key1, "one")).toEqual(0); + const key1 = "{key}" + uuidv4(); + const key2 = "{key}" + uuidv4(); + const key3 = "{key}" + uuidv4(); + const value = uuidv4(); + expect(await client.set(key1, value)).toEqual("OK"); + expect(await client.set(key2, value)).toEqual("OK"); + expect(await client.set(key3, value)).toEqual("OK"); + expect( + await client.unlink([ + key1, + key2, + "nonExistingKey", + Buffer.from(key3), + ]), + ).toEqual(3); + }, protocol); + }, + config.timeout, + ); - if (!(await checkIfServerVersionLessThan("7.2.0"))) { - expect(await client.zrankWithScore(key1, "one")).toEqual([ - 0, 1.5, - ]); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `expire, pexpire and ttl with positive timeout_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + const key = uuidv4(); + expect(await client.set(key, "foo")).toEqual("OK"); + expect(await client.expire(key, 10)).toEqual(true); + expect(await client.ttl(key)).toBeLessThanOrEqual(10); + /// set command clears the timeout. + expect(await client.set(key, "bar")).toEqual("OK"); + const versionLessThan = + cluster.checkIfServerVersionLessThan("7.0.0"); + + if (versionLessThan) { expect( - await client.zrankWithScore(key1, "nonExistingMember"), - ).toEqual(null); + await client.pexpire(Buffer.from(key), 10000), + ).toEqual(true); + } else { expect( - await client.zrankWithScore("nonExistingKey", "member"), - ).toEqual(null); + await client.pexpire( + Buffer.from(key), + 10000, + ExpireOptions.HasNoExpiry, + ), + ).toEqual(true); } - expect(await client.zrank(key1, "nonExistingMember")).toEqual( - null, - ); - expect(await client.zrank("nonExistingKey", "member")).toEqual( - null, + expect(await client.ttl(Buffer.from(key))).toBeLessThanOrEqual( + 10, ); - checkSimple(await client.set(key2, "value")).toEqual("OK"); - await expect(client.zrank(key2, "member")).rejects.toThrow(); + /// TTL will be updated to the new value = 15 + if (versionLessThan) { + expect(await client.expire(Buffer.from(key), 15)).toEqual( + true, + ); + } else { + expect( + await client.expire( + Buffer.from(key), + 15, + ExpireOptions.HasExistingExpiry, + ), + ).toEqual(true); + expect(await client.expiretime(key)).toBeGreaterThan( + Math.floor(Date.now() / 1000), + ); + expect(await client.pexpiretime(key)).toBeGreaterThan( + Date.now(), + ); + // test Buffer input argument + expect( + await client.expiretime(Buffer.from(key)), + ).toBeGreaterThan(Math.floor(Date.now() / 1000)); + expect( + await client.pexpiretime(Buffer.from(key)), + ).toBeGreaterThan(Date.now()); + } + + expect(await client.ttl(key)).toBeLessThanOrEqual(15); }, protocol); }, + config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `test brpop test_%p`, + `expireAt, pexpireAt and ttl with positive timeout_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { + await runTest(async (client: BaseClient, cluster) => { + const key = uuidv4(); + expect(await client.set(key, "foo")).toEqual("OK"); expect( - await client.rpush("brpop-test", ["foo", "bar", "baz"]), - ).toEqual(3); - // Test basic usage - checkSimple(await client.brpop(["brpop-test"], 0.1)).toEqual([ - "brpop-test", - "baz", - ]); - // Delete all values from list - expect(await client.del(["brpop-test"])).toEqual(1); - // Test null return when key doesn't exist - expect(await client.brpop(["brpop-test"], 0.1)).toEqual(null); - // key exists, but it is not a list - await client.set("foo", "bar"); - await expect(client.brpop(["foo"], 0.1)).rejects.toThrow(); + await client.expireAt( + key, + Math.floor(Date.now() / 1000) + 10, + ), + ).toEqual(true); + expect(await client.ttl(key)).toBeLessThanOrEqual(10); + const versionLessThan = + cluster.checkIfServerVersionLessThan("7.0.0"); - // Same-slot requirement - if (client instanceof GlideClusterClient) { - try { - expect( - await client.brpop(["abc", "zxy", "lkn"], 0.1), - ).toThrow(); - } catch (e) { - expect((e as Error).message.toLowerCase()).toMatch( - "crossslot", - ); - } + if (versionLessThan) { + expect( + await client.expireAt( + Buffer.from(key), + Math.floor(Date.now() / 1000) + 50, + ), + ).toEqual(true); + } else { + expect( + await client.expireAt( + Buffer.from(key), + Math.floor(Date.now() / 1000) + 50, + ExpireOptions.NewExpiryGreaterThanCurrent, + ), + ).toEqual(true); + } + + expect(await client.ttl(key)).toBeLessThanOrEqual(50); + + /// set command clears the timeout. + expect(await client.set(key, "bar")).toEqual("OK"); + + if (!versionLessThan) { + expect( + await client.pexpireAt( + key, + Date.now() + 50000, + ExpireOptions.HasExistingExpiry, + ), + ).toEqual(false); + // test Buffer input argument + expect( + await client.pexpireAt( + Buffer.from(key), + Date.now() + 50000, + ExpireOptions.HasExistingExpiry, + ), + ).toEqual(false); } }, protocol); }, @@ -2786,885 +3816,7589 @@ export function runBaseTests(config: { ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `test blpop test_%p`, + `expire, pexpire, expireAt and pexpireAt with timestamp in the past or negative timeout_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { + await runTest(async (client: BaseClient, cluster) => { + const key = uuidv4(); + expect(await client.set(key, "foo")).toEqual("OK"); + expect(await client.ttl(key)).toEqual(-1); + expect(await client.expire(key, -10)).toEqual(true); + expect(await client.ttl(key)).toEqual(-2); + expect(await client.set(key, "foo")).toEqual("OK"); + expect(await client.pexpire(key, -10000)).toEqual(true); + expect(await client.ttl(key)).toEqual(-2); + expect(await client.set(key, "foo")).toEqual("OK"); expect( - await client.rpush("blpop-test", ["foo", "bar", "baz"]), - ).toEqual(3); - // Test basic usage - checkSimple(await client.blpop(["blpop-test"], 0.1)).toEqual([ - "blpop-test", - "foo", - ]); - // Delete all values from list - expect(await client.del(["blpop-test"])).toEqual(1); - // Test null return when key doesn't exist - expect(await client.blpop(["blpop-test"], 0.1)).toEqual(null); - // key exists, but it is not a list - await client.set("foo", "bar"); - await expect(client.blpop(["foo"], 0.1)).rejects.toThrow(); + await client.expireAt( + key, + Math.floor(Date.now() / 1000) - 50, /// timeout in the past + ), + ).toEqual(true); + expect(await client.ttl(key)).toEqual(-2); + expect(await client.set(key, "foo")).toEqual("OK"); - // Same-slot requirement - if (client instanceof GlideClusterClient) { - try { - expect( - await client.blpop(["abc", "zxy", "lkn"], 0.1), - ).toThrow(); - } catch (e) { - expect((e as Error).message.toLowerCase()).toMatch( - "crossslot", - ); - } + // no timeout set yet + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + expect(await client.expiretime(key)).toEqual(-1); + expect(await client.pexpiretime(key)).toEqual(-1); } + + expect( + await client.pexpireAt( + key, + Date.now() - 50000, /// timeout in the past + ), + ).toEqual(true); + expect(await client.ttl(key)).toEqual(-2); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `persist test_%p`, + `expire, pexpire, expireAt, pexpireAt and ttl with non-existing key_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { + await runTest(async (client: BaseClient, cluster) => { const key = uuidv4(); - checkSimple(await client.set(key, "foo")).toEqual("OK"); - expect(await client.persist(key)).toEqual(false); + expect(await client.expire(key, 10)).toEqual(false); + expect(await client.pexpire(key, 10000)).toEqual(false); + expect( + await client.expireAt( + key, + Math.floor(Date.now() / 1000) + 50, /// timeout in the past + ), + ).toEqual(false); + expect( + await client.pexpireAt( + key, + Date.now() + 50000, /// timeout in the past + ), + ).toEqual(false); + expect(await client.ttl(key)).toEqual(-2); - expect(await client.expire(key, 10)).toEqual(true); - expect(await client.persist(key)).toEqual(true); + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + expect(await client.expiretime(key)).toEqual(-2); + expect(await client.pexpiretime(key)).toEqual(-2); + } }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `streams add, trim, and len test_%p`, + `script test_decoder_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const nonExistingKey = uuidv4(); - const stringKey = uuidv4(); - const field1 = uuidv4(); - const field2 = uuidv4(); + const key1 = Buffer.from(uuidv4()); + const key2 = Buffer.from(uuidv4()); - const nullResult = await client.xadd( - key, - [ - [field1, "foo"], - [field2, "bar"], - ], - { - makeStream: false, - }, - ); - expect(nullResult).toBeNull(); + let script = new Script(Buffer.from("return 'Hello'")); + expect( + await client.invokeScript(script, { + decoder: Decoder.Bytes, + }), + ).toEqual(Buffer.from("Hello")); - const timestamp1 = await client.xadd( - key, - [ - [field1, "foo1"], - [field2, "bar1"], - ], - { id: "0-1" }, + script = new Script( + Buffer.from("return redis.call('SET', KEYS[1], ARGV[1])"), ); - checkSimple(timestamp1).toEqual("0-1"); expect( - await client.xadd(key, [ - [field1, "foo2"], - [field2, "bar2"], - ]), - ).not.toBeNull(); - expect(await client.xlen(key)).toEqual(2); + await client.invokeScript(script, { + keys: [key1], + args: [Buffer.from("value1")], + decoder: Decoder.Bytes, + }), + ).toEqual("OK"); - // this will trim the first entry. - const id = await client.xadd( - key, - [ - [field1, "foo3"], - [field2, "bar3"], - ], - { - trim: { - method: "maxlen", - threshold: 2, - exact: true, - }, - }, - ); - expect(id).not.toBeNull(); - expect(await client.xlen(key)).toEqual(2); + /// Reuse the same script with different parameters. + expect( + await client.invokeScript(script, { + keys: [key2], + args: [Buffer.from("value2")], + }), + ).toEqual("OK"); - // this will trim the 2nd entry. + script = new Script( + Buffer.from("return redis.call('GET', KEYS[1])"), + ); expect( - await client.xadd( - key, - [ - [field1, "foo4"], - [field2, "bar4"], - ], - { - trim: { - method: "minid", - threshold: id as string, - exact: true, - }, - }, - ), - ).not.toBeNull(); - expect(await client.xlen(key)).toEqual(2); + await client.invokeScript(script, { keys: [key1] }), + ).toEqual("value1"); expect( - await client.xtrim(key, { - method: "maxlen", - threshold: 1, - exact: true, + await client.invokeScript(script, { keys: [key2] }), + ).toEqual("value2"); + // Get bytes rsponse + expect( + await client.invokeScript(script, { + keys: [key1], + decoder: Decoder.Bytes, }), - ).toEqual(1); - expect(await client.xlen(key)).toEqual(1); + ).toEqual(Buffer.from("value1")); expect( - await client.xtrim(key, { - method: "maxlen", - threshold: 0, - exact: true, + await client.invokeScript(script, { + keys: [key2], + decoder: Decoder.Bytes, }), - ).toEqual(1); - // Unlike other Redis collection types, stream keys still exist even after removing all entries - expect(await client.exists([key])).toEqual(1); - expect(await client.xlen(key)).toEqual(0); + ).toEqual(Buffer.from("value2")); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `script test_binary_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = Buffer.from(uuidv4()); + const key2 = Buffer.from(uuidv4()); + let script = new Script(Buffer.from("return 'Hello'")); + expect(await client.invokeScript(script)).toEqual("Hello"); + + script = new Script( + Buffer.from("return redis.call('SET', KEYS[1], ARGV[1])"), + ); expect( - await client.xtrim(nonExistingKey, { - method: "maxlen", - threshold: 1, - exact: true, + await client.invokeScript(script, { + keys: [key1], + args: [Buffer.from("value1")], }), - ).toEqual(0); - expect(await client.xlen(nonExistingKey)).toEqual(0); + ).toEqual("OK"); - // key exists, but it is not a stream - expect(await client.set(stringKey, "foo")).toEqual("OK"); - await expect( - client.xtrim(stringKey, { - method: "maxlen", - threshold: 1, - exact: true, + /// Reuse the same script with different parameters. + expect( + await client.invokeScript(script, { + keys: [key2], + args: [Buffer.from("value2")], }), - ).rejects.toThrow(); - await expect(client.xlen(stringKey)).rejects.toThrow(); + ).toEqual("OK"); + + script = new Script( + Buffer.from("return redis.call('GET', KEYS[1])"), + ); + expect( + await client.invokeScript(script, { keys: [key1] }), + ).toEqual("value1"); + + expect( + await client.invokeScript(script, { keys: [key2] }), + ).toEqual("value2"); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `zremRangeByScore test_%p`, + `script test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const membersScores = { one: 1, two: 2, three: 3 }; - expect(await client.zadd(key, membersScores)).toEqual(3); + const key1 = uuidv4(); + const key2 = uuidv4(); + + let script = new Script("return 'Hello'"); + expect(await client.invokeScript(script)).toEqual("Hello"); + + script = new Script( + "return redis.call('SET', KEYS[1], ARGV[1])", + ); + expect( + await client.invokeScript(script, { + keys: [key1], + args: ["value1"], + }), + ).toEqual("OK"); + /// Reuse the same script with different parameters. expect( - await client.zremRangeByScore( - key, - { value: 1, isInclusive: false }, - { value: 2 }, - ), - ).toEqual(1); + await client.invokeScript(script, { + keys: [key2], + args: ["value2"], + }), + ).toEqual("OK"); + script = new Script("return redis.call('GET', KEYS[1])"); expect( - await client.zremRangeByScore( - key, - { value: 1 }, - "negativeInfinity", - ), - ).toEqual(0); + await client.invokeScript(script, { keys: [key1] }), + ).toEqual("value1"); expect( - await client.zremRangeByScore( - "nonExistingKey", - "negativeInfinity", - "positiveInfinity", - ), - ).toEqual(0); + await client.invokeScript(script, { keys: [key2] }), + ).toEqual("value2"); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "time test_%p", + `zadd and zaddIncr test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - // Take the time now, convert to 10 digits and subtract 1 second - const now = Math.floor(new Date().getTime() / 1000 - 1); - const result = (await client.time()) as [string, string]; - expect(result?.length).toEqual(2); - expect(Number(result?.at(0))).toBeGreaterThan(now); - // Test its not more than 1 second - expect(Number(result?.at(1))).toBeLessThan(1000000); + const key = uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + const newMembersScores = { one: 2, two: 3 }; + + expect(await client.zadd(key, membersScores)).toEqual(3); + expect(await client.zaddIncr(key, "one", 2)).toEqual(3.0); + expect( + await client.zadd(key, newMembersScores, { changed: true }), + ).toEqual(2); }, protocol); }, + config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `streams read test_%p`, - async () => { + `zadd and zaddIncr with NX XX test_%p`, + async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = uuidv4(); - const key2 = `{${key1}}${uuidv4()}`; - const field1 = "foo"; - const field2 = "bar"; - const field3 = "barvaz"; + const key = uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect( + await client.zadd(key, membersScores, { + conditionalChange: ConditionalChange.ONLY_IF_EXISTS, + }), + ).toEqual(0); - const timestamp_1_1 = await client.xadd(key1, [ - [field1, "foo1"], - [field3, "barvaz1"], - ]); - expect(timestamp_1_1).not.toBeNull(); - const timestamp_2_1 = await client.xadd(key2, [ - [field2, "bar1"], - ]); - expect(timestamp_2_1).not.toBeNull(); - const timestamp_1_2 = await client.xadd(key1, [ - [field1, "foo2"], - ]); - const timestamp_2_2 = await client.xadd(key2, [ - [field2, "bar2"], - ]); - const timestamp_1_3 = await client.xadd(key1, [ - [field1, "foo3"], - [field3, "barvaz3"], - ]); - const timestamp_2_3 = await client.xadd(key2, [ - [field2, "bar3"], - ]); + expect( + await client.zadd(key, membersScores, { + conditionalChange: + ConditionalChange.ONLY_IF_DOES_NOT_EXIST, + }), + ).toEqual(3); - const result = await client.xread( - { - [key1]: timestamp_1_1 as string, - [key2]: timestamp_2_1 as string, - }, - { - block: 1, - }, - ); + expect( + await client.zaddIncr(key, "one", 5.0, { + conditionalChange: + ConditionalChange.ONLY_IF_DOES_NOT_EXIST, + }), + ).toEqual(null); - const expected = { - [key1]: { - [timestamp_1_2 as string]: [[field1, "foo2"]], - [timestamp_1_3 as string]: [ - [field1, "foo3"], - [field3, "barvaz3"], - ], - }, - [key2]: { - [timestamp_2_2 as string]: [["bar", "bar2"]], - [timestamp_2_3 as string]: [["bar", "bar3"]], - }, - }; - checkSimple(result).toEqual(expected); - }, ProtocolVersion.RESP2); + expect( + await client.zaddIncr(key, "one", 5.0, { + conditionalChange: ConditionalChange.ONLY_IF_EXISTS, + }), + ).toEqual(6.0); + }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "rename test_%p", + `zadd and zaddIncr with GT LT test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - // Making sure both keys will be oart of the same slot - const key = uuidv4() + "{123}"; - const newKey = uuidv4() + "{123}"; - await client.set(key, "value"); - await client.rename(key, newKey); - const result = await client.get(newKey); - checkSimple(result).toEqual("value"); - // If key doesn't exist it should throw, it also test that key has successfully been renamed - await expect(client.rename(key, newKey)).rejects.toThrow(); + const key = uuidv4(); + const membersScores = { one: -3, two: 2, three: 3 }; + + expect(await client.zadd(key, membersScores)).toEqual(3); + membersScores["one"] = 10; + + expect( + await client.zadd(key, membersScores, { + updateOptions: UpdateByScore.GREATER_THAN, + changed: true, + }), + ).toEqual(1); + + expect( + await client.zadd(key, membersScores, { + updateOptions: UpdateByScore.LESS_THAN, + changed: true, + }), + ).toEqual(0); + + expect( + await client.zaddIncr(key, "one", -3.0, { + updateOptions: UpdateByScore.LESS_THAN, + }), + ).toEqual(7.0); + + expect( + await client.zaddIncr(key, "one", -3.0, { + updateOptions: UpdateByScore.GREATER_THAN, + }), + ).toEqual(null); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "renamenx test_%p", + `zrem test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - const key1 = `{key}-1-${uuidv4()}`; - const key2 = `{key}-2-${uuidv4()}`; - const key3 = `{key}-3-${uuidv4()}`; - - // renamenx missing key - try { - expect(await client.renamenx(key1, key2)).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch("no such key"); - } - - // renamenx a string - await client.set(key1, "key1"); - await client.set(key3, "key3"); - // Test that renamenx can rename key1 to key2 (non-existing value) - checkSimple(await client.renamenx(key1, key2)).toEqual(true); - // sanity check - checkSimple(await client.get(key2)).toEqual("key1"); - // Test that renamenx doesn't rename key2 to key3 (with an existing value) - checkSimple(await client.renamenx(key2, key3)).toEqual(false); - // sanity check - checkSimple(await client.get(key3)).toEqual("key3"); + const key = uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + expect(await client.zrem(key, ["one"])).toEqual(1); + expect(await client.zrem(key, ["one", "two", "three"])).toEqual( + 2, + ); + expect( + await client.zrem("non_existing_set", ["member"]), + ).toEqual(0); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "pfadd test_%p", + `zcard test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { const key = uuidv4(); - checkSimple(await client.pfadd(key, [])).toEqual(1); - checkSimple(await client.pfadd(key, ["one", "two"])).toEqual(1); - checkSimple(await client.pfadd(key, ["two"])).toEqual(0); - checkSimple(await client.pfadd(key, [])).toEqual(0); - - // key exists, but it is not a HyperLogLog - checkSimple(await client.set("foo", "value")).toEqual("OK"); - await expect(client.pfadd("foo", [])).rejects.toThrow(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + expect(await client.zcard(key)).toEqual(3); + expect(await client.zrem(key, ["one"])).toEqual(1); + expect(await client.zcard(Buffer.from(key))).toEqual(2); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "pfcount test_%p", + `zintercard test_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { - const key1 = `{key}-1-${uuidv4()}`; - const key2 = `{key}-2-${uuidv4()}`; - const key3 = `{key}-3-${uuidv4()}`; - const stringKey = `{key}-4-${uuidv4()}`; - const nonExistingKey = `{key}-5-${uuidv4()}`; + await runTest(async (client: BaseClient, cluster) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) { + return; + } + + const key1 = `{key}:${uuidv4()}`; + const key2 = `{key}:${uuidv4()}`; + const stringKey = `{key}:${uuidv4()}`; + const nonExistingKey = `{key}:${uuidv4()}`; + const memberScores1 = { one: 1, two: 2, three: 3 }; + const memberScores2 = { two: 2, three: 3, four: 4 }; + + expect(await client.zadd(key1, memberScores1)).toEqual(3); + expect(await client.zadd(key2, memberScores2)).toEqual(3); - expect(await client.pfadd(key1, ["a", "b", "c"])).toEqual(1); - expect(await client.pfadd(key2, ["b", "c", "d"])).toEqual(1); - expect(await client.pfcount([key1])).toEqual(3); - expect(await client.pfcount([key2])).toEqual(3); - expect(await client.pfcount([key1, key2])).toEqual(4); expect( - await client.pfcount([key1, key2, nonExistingKey]), - ).toEqual(4); + await client.zintercard([key1, Buffer.from(key2)]), + ).toEqual(2); + expect(await client.zintercard([key1, nonExistingKey])).toEqual( + 0, + ); - // empty HyperLogLog data set - expect(await client.pfadd(key3, [])).toEqual(1); - expect(await client.pfcount([key3])).toEqual(0); + expect(await client.zintercard([key1, key2], 0)).toEqual(2); + expect(await client.zintercard([key1, key2], 1)).toEqual(1); + expect(await client.zintercard([key1, key2], 2)).toEqual(2); // invalid argument - key list must not be empty - try { - expect(await client.pfcount([])).toThrow(); - } catch (e) { - expect((e as Error).message).toMatch( - "ResponseError: wrong number of arguments", - ); - } + await expect(client.zintercard([])).rejects.toThrow(); - // key exists, but it is not a HyperLogLog - expect(await client.set(stringKey, "value")).toEqual("OK"); - await expect(client.pfcount([stringKey])).rejects.toThrow(); + // invalid argument - limit must be non-negative + await expect( + client.zintercard([key1, key2], -1), + ).rejects.toThrow(); + + // key exists, but it is not a sorted set + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect(client.zintercard([stringKey])).rejects.toThrow(); }, protocol); }, config.timeout, ); - // Set command tests + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zdiff test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) { + return; + } - async function setWithExpiryOptions(client: BaseClient) { - const key = uuidv4(); - const value = uuidv4(); - const setResWithExpirySetMilli = await client.set(key, value, { - expiry: { - type: "milliseconds", - count: 500, - }, - }); - checkSimple(setResWithExpirySetMilli).toEqual("OK"); - const getWithExpirySetMilli = await client.get(key); - checkSimple(getWithExpirySetMilli).toEqual(value); + const key1 = `{key}-${uuidv4()}`; + const key2 = `{key}-${uuidv4()}`; + const key3 = `{key}-${uuidv4()}`; + const nonExistingKey = `{key}-${uuidv4()}`; + const stringKey = `{key}-${uuidv4()}`; - const setResWithExpirySec = await client.set(key, value, { - expiry: { - type: "seconds", - count: 1, - }, - }); - checkSimple(setResWithExpirySec).toEqual("OK"); - const getResWithExpirySec = await client.get(key); - checkSimple(getResWithExpirySec).toEqual(value); + const entries1 = { + one: 1.0, + two: 2.0, + three: 3.0, + }; + const entries2 = { two: 2.0 }; + const entries3 = { + one: 1.0, + two: 2.0, + three: 3.0, + four: 4.0, + }; - const setWithUnixSec = await client.set(key, value, { - expiry: { - type: "unixSeconds", - count: Math.floor(Date.now() / 1000) + 1, - }, - }); - checkSimple(setWithUnixSec).toEqual("OK"); - const getWithUnixSec = await client.get(key); - checkSimple(getWithUnixSec).toEqual(value); + expect(await client.zadd(key1, entries1)).toEqual(3); + expect(await client.zadd(key2, entries2)).toEqual(1); + expect(await client.zadd(key3, entries3)).toEqual(4); - const setResWithExpiryKeep = await client.set(key, value, { - expiry: "keepExisting", - }); - checkSimple(setResWithExpiryKeep).toEqual("OK"); - const getResWithExpiryKeep = await client.get(key); - checkSimple(getResWithExpiryKeep).toEqual(value); - // wait for the key to expire base on the previous set - let sleep = new Promise((resolve) => setTimeout(resolve, 1000)); - await sleep; - const getResExpire = await client.get(key); - // key should have expired - checkSimple(getResExpire).toEqual(null); - const setResWithExpiryWithUmilli = await client.set(key, value, { - expiry: { - type: "unixMilliseconds", - count: Date.now() + 1000, - }, - }); - checkSimple(setResWithExpiryWithUmilli).toEqual("OK"); - // wait for the key to expire - sleep = new Promise((resolve) => setTimeout(resolve, 1001)); - await sleep; - const getResWithExpiryWithUmilli = await client.get(key); - // key should have expired - checkSimple(getResWithExpiryWithUmilli).toEqual(null); - } + expect(await client.zdiff([key1, Buffer.from(key2)])).toEqual([ + "one", + "three", + ]); + expect( + await client.zdiff([key1, key2], { + decoder: Decoder.Bytes, + }), + ).toEqual([Buffer.from("one"), Buffer.from("three")]); + expect(await client.zdiff([key1, key3])).toEqual([]); + expect(await client.zdiff([nonExistingKey, key3])).toEqual([]); - async function setWithOnlyIfExistOptions(client: BaseClient) { - const key = uuidv4(); - const value = uuidv4(); - const setKey = await client.set(key, value); - checkSimple(setKey).toEqual("OK"); - const getRes = await client.get(key); - checkSimple(getRes).toEqual(value); - const setExistingKeyRes = await client.set(key, value, { - conditionalSet: "onlyIfExists", - }); - checkSimple(setExistingKeyRes).toEqual("OK"); - const getExistingKeyRes = await client.get(key); - checkSimple(getExistingKeyRes).toEqual(value); + let result = await client.zdiffWithScores([key1, key2]); + const expected = { + one: 1.0, + three: 3.0, + }; + expect(compareMaps(result, expected)).toBe(true); + // same with byte[] + result = await client.zdiffWithScores([ + key1, + Buffer.from(key2), + ]); + expect(compareMaps(result, expected)).toBe(true); - const notExistingKeyRes = await client.set(key + 1, value, { - conditionalSet: "onlyIfExists", - }); - // key does not exist, so it should not be set - checkSimple(notExistingKeyRes).toEqual(null); - const getNotExistingKey = await client.get(key + 1); - // key should not have been set - checkSimple(getNotExistingKey).toEqual(null); - } + result = await client.zdiffWithScores([key1, key3]); + expect(compareMaps(result, {})).toBe(true); - async function setWithOnlyIfNotExistOptions(client: BaseClient) { - const key = uuidv4(); - const value = uuidv4(); - const notExistingKeyRes = await client.set(key, value, { - conditionalSet: "onlyIfDoesNotExist", - }); - // key does not exist, so it should be set - checkSimple(notExistingKeyRes).toEqual("OK"); - const getNotExistingKey = await client.get(key); - // key should have been set - checkSimple(getNotExistingKey).toEqual(value); + result = await client.zdiffWithScores([nonExistingKey, key3]); + expect(compareMaps(result, {})).toBe(true); - const existingKeyRes = await client.set(key, value, { - conditionalSet: "onlyIfDoesNotExist", - }); - // key exists, so it should not be set - checkSimple(existingKeyRes).toEqual(null); - const getExistingKey = await client.get(key); - // key should not have been set - checkSimple(getExistingKey).toEqual(value); + // invalid arg - key list must not be empty + await expect(client.zdiff([])).rejects.toThrow(RequestError); + await expect(client.zdiffWithScores([])).rejects.toThrow( + RequestError, + ); + + // key exists, but it is not a sorted set + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect(client.zdiff([stringKey, key1])).rejects.toThrow(); + await expect( + client.zdiffWithScores([stringKey, key1]), + ).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zdiffstore test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) { + return; + } + + const key1 = `{key}-${uuidv4()}`; + const key2 = `{key}-${uuidv4()}`; + const key3 = `{key}-${uuidv4()}`; + const key4 = `{key}-${uuidv4()}`; + const nonExistingKey = `{key}-${uuidv4()}`; + const stringKey = `{key}-${uuidv4()}`; + + const entries1 = { + one: 1.0, + two: 2.0, + three: 3.0, + }; + const entries2 = { two: 2.0 }; + const entries3 = { + one: 1.0, + two: 2.0, + three: 3.0, + four: 4.0, + }; + + expect(await client.zadd(key1, entries1)).toEqual(3); + expect(await client.zadd(key2, entries2)).toEqual(1); + expect(await client.zadd(key3, entries3)).toEqual(4); + + expect(await client.zdiffstore(key4, [key1, key2])).toEqual(2); + const result1 = await client.zrangeWithScores(key4, { + start: 0, + stop: -1, + }); + const expected1 = { one: 1.0, three: 3.0 }; + expect(compareMaps(result1, expected1)).toBe(true); + + expect( + await client.zdiffstore(Buffer.from(key4), [ + key3, + key2, + key1, + ]), + ).toEqual(1); + const result2 = await client.zrangeWithScores(key4, { + start: 0, + stop: -1, + }); + expect(compareMaps(result2, { four: 4.0 })).toBe(true); + + expect( + await client.zdiffstore(key4, [Buffer.from(key1), key3]), + ).toEqual(0); + const result3 = await client.zrangeWithScores(key4, { + start: 0, + stop: -1, + }); + expect(compareMaps(result3, {})).toBe(true); + + expect( + await client.zdiffstore(key4, [nonExistingKey, key1]), + ).toEqual(0); + const result4 = await client.zrangeWithScores(key4, { + start: 0, + stop: -1, + }); + expect(compareMaps(result4, {})).toBe(true); + + // invalid arg - key list must not be empty + await expect(client.zdiffstore(key4, [])).rejects.toThrow( + RequestError, + ); + + // key exists, but it is not a sorted set + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.zdiffstore(key4, [stringKey, key1]), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zscore test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect(await client.zadd(key1, membersScores)).toEqual(3); + expect(await client.zscore(key1, "one")).toEqual(1.0); + expect(await client.zscore(key1, "nonExistingMember")).toEqual( + null, + ); + expect( + await client.zscore("nonExistingKey", "nonExistingMember"), + ).toEqual(null); + + expect(await client.set(key2, "foo")).toEqual("OK"); + await expect(client.zscore(key2, "foo")).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + // ZUnionStore command tests + async function zunionStoreWithMaxAggregation(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + const key3 = "{testKey}:3-" + uuidv4(); + const range = { + start: 0, + stop: -1, + }; + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Union results are aggregated by the MAX score of elements + expect(await client.zunionstore(key3, [key1, key2], "MAX")).toEqual(3); + const zunionstoreMapMax = await client.zrangeWithScores(key3, range); + const expectedMapMax = { + one: 1.5, + two: 2.5, + three: 3.5, + }; + expect(zunionstoreMapMax).toEqual(expectedMapMax); + } + + async function zunionStoreWithMinAggregation(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + const key3 = "{testKey}:3-" + uuidv4(); + const range = { + start: 0, + stop: -1, + }; + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Union results are aggregated by the MIN score of elements + expect(await client.zunionstore(key3, [key1, key2], "MIN")).toEqual(3); + const zunionstoreMapMin = await client.zrangeWithScores(key3, range); + const expectedMapMin = { + one: 1.0, + two: 2.0, + three: 3.5, + }; + expect(zunionstoreMapMin).toEqual(expectedMapMin); } - async function setWithGetOldOptions(client: BaseClient) { - const key = uuidv4(); - const value = uuidv4(); + async function zunionStoreWithSumAggregation(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + const key3 = "{testKey}:3-" + uuidv4(); + const range = { + start: 0, + stop: -1, + }; + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Union results are aggregated by the SUM score of elements + expect(await client.zunionstore(key3, [key1, key2], "SUM")).toEqual(3); + const zunionstoreMapSum = await client.zrangeWithScores(key3, range); + const expectedMapSum = { + one: 2.5, + two: 4.5, + three: 3.5, + }; + expect(zunionstoreMapSum).toEqual(expectedMapSum); + } + + async function zunionStoreBasicTest(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + const key3 = "{testKey}:3-" + uuidv4(); + const range = { + start: 0, + stop: -1, + }; + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 2.0, two: 3.0, three: 4.0 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + expect(await client.zunionstore(key3, [key1, key2])).toEqual(3); + const zunionstoreMap = await client.zrangeWithScores(key3, range); + const expectedMap = { + one: 3.0, + three: 4.0, + two: 5.0, + }; + expect(zunionstoreMap).toEqual(expectedMap); + } + + async function zunionStoreWithWeightsAndAggregation(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + const key3 = "{testKey}:3-" + uuidv4(); + const range = { + start: 0, + stop: -1, + }; + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Scores are multiplied by 2.0 for key1 and key2 during aggregation. + expect( + await client.zunionstore( + key3, + [ + [key1, 2.0], + [key2, 2.0], + ], + "SUM", + ), + ).toEqual(3); + const zunionstoreMapMultiplied = await client.zrangeWithScores( + key3, + range, + ); + const expectedMapMultiplied = { + one: 5.0, + three: 7.0, + two: 9.0, + }; + expect(zunionstoreMapMultiplied).toEqual(expectedMapMultiplied); + } + + async function zunionStoreEmptyCases(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + const range = { + start: 0, + stop: -1, + }; + const membersScores1 = { one: 1.0, two: 2.0 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + + // Non existing key + expect( + await client.zunionstore(key2, [ + key1, + "{testKey}-non_existing_key", + ]), + ).toEqual(2); + + const zunionstore_map_nonexistingkey = await client.zrangeWithScores( + key2, + range, + ); + + const expectedMapMultiplied = { + one: 1.0, + two: 2.0, + }; + expect(zunionstore_map_nonexistingkey).toEqual(expectedMapMultiplied); + + // Empty list check + await expect(client.zunionstore("{xyz}", [])).rejects.toThrow(); + } + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zunionstore test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + await zunionStoreBasicTest(client); + await zunionStoreWithMaxAggregation(client); + await zunionStoreWithMinAggregation(client); + await zunionStoreWithSumAggregation(client); + await zunionStoreWithWeightsAndAggregation(client); + await zunionStoreEmptyCases(client); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zmscore test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) { + return; + } + + const key1 = `{key}-${uuidv4()}`; + const nonExistingKey = `{key}-${uuidv4()}`; + const stringKey = `{key}-${uuidv4()}`; + + const entries = { + one: 1.0, + two: 2.0, + three: 3.0, + }; + expect(await client.zadd(key1, entries)).toEqual(3); + + expect( + await client.zmscore(key1, ["one", "three", "two"]), + ).toEqual([1.0, 3.0, 2.0]); + expect( + await client.zmscore(key1, [ + "one", + "nonExistingMember", + "two", + "nonExistingMember", + ]), + ).toEqual([1.0, null, 2.0, null]); + expect(await client.zmscore(nonExistingKey, ["one"])).toEqual([ + null, + ]); + + // invalid arg - member list must not be empty + await expect(client.zmscore(key1, [])).rejects.toThrow( + RequestError, + ); + + // key exists, but it is not a sorted set + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.zmscore(stringKey, ["one"]), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zcount test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect(await client.zadd(key1, membersScores)).toEqual(3); + expect( + await client.zcount( + key1, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).toEqual(3); + expect( + await client.zcount( + key1, + { value: 1, isInclusive: false }, + { value: 3, isInclusive: false }, + ), + ).toEqual(1); + expect( + await client.zcount( + key1, + { value: 1, isInclusive: false }, + { value: 3 }, + ), + ).toEqual(2); + expect( + await client.zcount( + Buffer.from(key1), + InfBoundary.NegativeInfinity, + { + value: 3, + }, + ), + ).toEqual(3); + expect( + await client.zcount(key1, InfBoundary.PositiveInfinity, { + value: 3, + }), + ).toEqual(0); + expect( + await client.zcount( + Buffer.from("nonExistingKey"), + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).toEqual(0); + + expect(await client.set(key2, "foo")).toEqual("OK"); + await expect( + client.zcount( + key2, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrange by index test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + + expect(await client.zrange(key, { start: 0, stop: 1 })).toEqual( + ["one", "two"], + ); + const result = await client.zrangeWithScores(key, { + start: 0, + stop: -1, + }); + + expect( + compareMaps(result, { + one: 1.0, + two: 2.0, + three: 3.0, + }), + ).toBe(true); + expect( + await client.zrange(key, { start: 0, stop: 1 }, true), + ).toEqual(["three", "two"]); + expect(await client.zrange(key, { start: 3, stop: 1 })).toEqual( + [], + ); + expect( + await client.zrangeWithScores(key, { start: 3, stop: 1 }), + ).toEqual({}); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrange by score test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + + expect( + await client.zrange(key, { + start: InfBoundary.NegativeInfinity, + stop: { value: 3, isInclusive: false }, + type: "byScore", + }), + ).toEqual(["one", "two"]); + const result = await client.zrangeWithScores(key, { + start: InfBoundary.NegativeInfinity, + stop: InfBoundary.PositiveInfinity, + type: "byScore", + }); + + expect( + compareMaps(result, { + one: 1.0, + two: 2.0, + three: 3.0, + }), + ).toBe(true); + expect( + await client.zrange( + key, + { + start: { value: 3, isInclusive: false }, + stop: InfBoundary.NegativeInfinity, + type: "byScore", + }, + true, + ), + ).toEqual(["two", "one"]); + + expect( + await client.zrange(key, { + start: InfBoundary.NegativeInfinity, + stop: InfBoundary.PositiveInfinity, + limit: { offset: 1, count: 2 }, + type: "byScore", + }), + ).toEqual(["two", "three"]); + + expect( + await client.zrange( + key, + { + start: InfBoundary.NegativeInfinity, + stop: { value: 3, isInclusive: false }, + type: "byScore", + }, + true, + ), + ).toEqual([]); + + expect( + await client.zrange(key, { + start: InfBoundary.PositiveInfinity, + stop: { value: 3, isInclusive: false }, + type: "byScore", + }), + ).toEqual([]); + + expect( + await client.zrangeWithScores( + key, + { + start: InfBoundary.NegativeInfinity, + stop: { value: 3, isInclusive: false }, + type: "byScore", + }, + true, + ), + ).toEqual({}); + + expect( + await client.zrangeWithScores(key, { + start: InfBoundary.PositiveInfinity, + stop: { value: 3, isInclusive: false }, + type: "byScore", + }), + ).toEqual({}); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrange by lex test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const membersScores = { a: 1, b: 2, c: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + + expect( + await client.zrange(key, { + start: InfBoundary.NegativeInfinity, + stop: { value: "c", isInclusive: false }, + type: "byLex", + }), + ).toEqual(["a", "b"]); + + expect( + await client.zrange(key, { + start: InfBoundary.NegativeInfinity, + stop: InfBoundary.PositiveInfinity, + limit: { offset: 1, count: 2 }, + type: "byLex", + }), + ).toEqual(["b", "c"]); + + expect( + await client.zrange( + key, + { + start: { value: "c", isInclusive: false }, + stop: InfBoundary.NegativeInfinity, + type: "byLex", + }, + true, + ), + ).toEqual(["b", "a"]); + + expect( + await client.zrange( + key, + { + start: InfBoundary.NegativeInfinity, + stop: { value: "c", isInclusive: false }, + type: "byLex", + }, + true, + ), + ).toEqual([]); + + expect( + await client.zrange(key, { + start: InfBoundary.PositiveInfinity, + stop: { value: "c", isInclusive: false }, + type: "byLex", + }), + ).toEqual([]); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrangeStore by index test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + + const key = "{testKey}:1-" + uuidv4(); + const destkey = "{testKey}:2-" + uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + + expect( + await client.zrangeStore(destkey, key, { + start: 0, + stop: 1, + }), + ).toEqual(2); + expect( + await client.zrange(destkey, { + start: 0, + stop: -1, + }), + ).toEqual(["one", "two"]); + + expect( + await client.zrangeStore( + destkey, + key, + { start: 0, stop: 1 }, + true, + ), + ).toEqual(2); + expect( + await client.zrange( + destkey, + { + start: 0, + stop: -1, + }, + true, + ), + ).toEqual(["three", "two"]); + + expect( + await client.zrangeStore(destkey, key, { + start: 3, + stop: 1, + }), + ).toEqual(0); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrangeStore by score test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key = "{testKey}:1-" + uuidv4(); + const destkey = "{testKey}:2-" + uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + + expect( + await client.zrangeStore(destkey, key, { + start: InfBoundary.NegativeInfinity, + stop: { value: 3, isInclusive: false }, + type: "byScore", + }), + ).toEqual(2); + expect( + await client.zrange(destkey, { + start: 0, + stop: -1, + }), + ).toEqual(["one", "two"]); + + expect( + await client.zrangeStore( + destkey, + key, + { + start: { value: 3, isInclusive: false }, + stop: InfBoundary.NegativeInfinity, + type: "byScore", + }, + true, + ), + ).toEqual(2); + expect( + await client.zrange( + destkey, + { + start: 0, + stop: -1, + }, + true, + ), + ).toEqual(["two", "one"]); + + expect( + await client.zrangeStore(destkey, key, { + start: InfBoundary.NegativeInfinity, + stop: InfBoundary.PositiveInfinity, + limit: { offset: 1, count: 2 }, + type: "byScore", + }), + ).toEqual(2); + expect( + await client.zrange(destkey, { + start: 0, + stop: -1, + }), + ).toEqual(["two", "three"]); + + expect( + await client.zrangeStore( + destkey, + key, + { + start: InfBoundary.NegativeInfinity, + stop: { value: 3, isInclusive: false }, + type: "byScore", + }, + true, + ), + ).toEqual(0); + + expect( + await client.zrangeStore(destkey, key, { + start: InfBoundary.PositiveInfinity, + stop: { value: 3, isInclusive: false }, + type: "byScore", + }), + ).toEqual(0); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrangeStore by lex test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key = "{testKey}:1-" + uuidv4(); + const destkey = "{testKey}:2-" + uuidv4(); + const membersScores = { a: 1, b: 2, c: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + + expect( + await client.zrangeStore(destkey, key, { + start: InfBoundary.NegativeInfinity, + stop: { value: "c", isInclusive: false }, + type: "byLex", + }), + ).toEqual(2); + expect( + await client.zrange(destkey, { + start: 0, + stop: -1, + }), + ).toEqual(["a", "b"]); + + expect( + await client.zrangeStore(destkey, key, { + start: InfBoundary.NegativeInfinity, + stop: InfBoundary.PositiveInfinity, + limit: { offset: 1, count: 2 }, + type: "byLex", + }), + ).toEqual(2); + expect( + await client.zrange(destkey, { + start: 0, + stop: -1, + }), + ).toEqual(["b", "c"]); + + expect( + await client.zrangeStore( + destkey, + key, + { + start: { value: "c", isInclusive: false }, + stop: InfBoundary.NegativeInfinity, + type: "byLex", + }, + true, + ), + ).toEqual(2); + expect( + await client.zrange( + destkey, + { + start: 0, + stop: -1, + }, + true, + ), + ).toEqual(["b", "a"]); + + expect( + await client.zrangeStore( + destkey, + key, + { + start: InfBoundary.NegativeInfinity, + stop: { value: "c", isInclusive: false }, + type: "byLex", + }, + true, + ), + ).toEqual(0); + + expect( + await client.zrangeStore(destkey, key, { + start: InfBoundary.PositiveInfinity, + stop: { value: "c", isInclusive: false }, + type: "byLex", + }), + ).toEqual(0); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrange and zrangeStore different types of keys test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + const key = "{testKey}:1-" + uuidv4(); + const nonExistingKey = "{testKey}:2-" + uuidv4(); + const destkey = "{testKey}:3-" + uuidv4(); + + // test non-existing key - return an empty set + expect( + await client.zrange(nonExistingKey, { + start: 0, + stop: 1, + }), + ).toEqual([]); + + expect( + await client.zrangeWithScores(nonExistingKey, { + start: 0, + stop: 1, + }), + ).toEqual({}); + + // test against a non-sorted set - throw RequestError + expect(await client.set(key, "value")).toEqual("OK"); + + await expect( + client.zrange(key, { start: 0, stop: 1 }), + ).rejects.toThrow(); + + await expect( + client.zrangeWithScores(key, { start: 0, stop: 1 }), + ).rejects.toThrow(); + + // test zrangeStore - added in version 6.2.0 + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + + // test non-existing key - stores an empty set + expect( + await client.zrangeStore(destkey, nonExistingKey, { + start: 0, + stop: 1, + }), + ).toEqual(0); + + // test against a non-sorted set - throw RequestError + await expect( + client.zrangeStore(destkey, key, { start: 0, stop: 1 }), + ).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + // Zinterstore command tests + async function zinterstoreWithAggregation(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + const key3 = "{testKey}:3-" + uuidv4(); + const range = { + start: 0, + stop: -1, + }; + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 2.0, two: 3.0, three: 4.0 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Intersection results are aggregated by the MAX score of elements + expect(await client.zinterstore(key3, [key1, key2], "MAX")).toEqual(2); + const zinterstoreMapMax = await client.zrangeWithScores(key3, range); + const expectedMapMax = { + one: 2, + two: 3, + }; + expect(compareMaps(zinterstoreMapMax, expectedMapMax)).toBe(true); + + // Intersection results are aggregated by the MIN score of elements + expect( + await client.zinterstore(Buffer.from(key3), [key1, key2], "MIN"), + ).toEqual(2); + const zinterstoreMapMin = await client.zrangeWithScores(key3, range); + const expectedMapMin = { + one: 1, + two: 2, + }; + expect(compareMaps(zinterstoreMapMin, expectedMapMin)).toBe(true); + + // Intersection results are aggregated by the SUM score of elements + expect( + await client.zinterstore(key3, [Buffer.from(key1), key2], "SUM"), + ).toEqual(2); + const zinterstoreMapSum = await client.zrangeWithScores(key3, range); + const expectedMapSum = { + one: 3, + two: 5, + }; + expect(compareMaps(zinterstoreMapSum, expectedMapSum)).toBe(true); + } + + async function zinterstoreBasicTest(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + const key3 = "{testKey}:3-" + uuidv4(); + const range = { + start: 0, + stop: -1, + }; + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 2.0, two: 3.0, three: 4.0 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + expect(await client.zinterstore(key3, [key1, key2])).toEqual(2); + const zinterstoreMap = await client.zrangeWithScores(key3, range); + const expectedMap = { + one: 3, + two: 5, + }; + expect(compareMaps(zinterstoreMap, expectedMap)).toBe(true); + } + + async function zinterstoreWithWeightsAndAggregation(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + const key3 = "{testKey}:3-" + uuidv4(); + const range = { + start: 0, + stop: -1, + }; + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 2.0, two: 3.0, three: 4.0 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Scores are multiplied by 2.0 for key1 and key2 during aggregation. + expect( + await client.zinterstore( + key3, + [ + [key1, 2.0], + [key2, 2.0], + ], + "SUM", + ), + ).toEqual(2); + const zinterstoreMapMultiplied = await client.zrangeWithScores( + key3, + range, + ); + const expectedMapMultiplied = { + one: 6, + two: 10, + }; + expect( + compareMaps(zinterstoreMapMultiplied, expectedMapMultiplied), + ).toBe(true); + } + + async function zinterstoreEmptyCases(client: BaseClient) { + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + // Non existing key + expect( + await client.zinterstore(key2, [ + key1, + "{testKey}-non_existing_key", + ]), + ).toEqual(0); + + // Empty list check + await expect(client.zinterstore("{xyz}", [])).rejects.toThrow(); + } + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zinterstore test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + await zinterstoreBasicTest(client); + await zinterstoreWithAggregation(client); + await zinterstoreWithWeightsAndAggregation(client); + await zinterstoreEmptyCases(client); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zinter basic test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + expect(await client.zinter([key1, key2])).toEqual([ + "one", + "two", + ]); + expect(await client.zinter([key1, Buffer.from(key2)])).toEqual([ + "one", + "two", + ]); + expect( + await client.zinter([key1, key2], { + decoder: Decoder.Bytes, + }), + ).toEqual([Buffer.from("one"), Buffer.from("two")]); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zinter with scores basic test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + const resultZinterWithScores = await client.zinterWithScores([ + key1, + Buffer.from(key2), + ]); + const expectedZinterWithScores = { + one: 2.5, + two: 4.5, + }; + expect(resultZinterWithScores).toEqual( + expectedZinterWithScores, + ); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zinter with scores with max aggregation test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Intersection results are aggregated by the MAX score of elements + const zinterWithScoresResults = await client.zinterWithScores( + [key1, key2], + { aggregationType: "MAX" }, + ); + const expectedMapMax = { + one: 1.5, + two: 2.5, + }; + expect(zinterWithScoresResults).toEqual(expectedMapMax); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zinter with scores with min aggregation test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Intersection results are aggregated by the MIN score of elements + const zinterWithScoresResults = await client.zinterWithScores( + [key1, key2], + { aggregationType: "MIN" }, + ); + const expectedMapMin = { + one: 1.0, + two: 2.0, + }; + expect(zinterWithScoresResults).toEqual(expectedMapMin); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zinter with scores with sum aggregation test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Intersection results are aggregated by the SUM score of elements + const zinterWithScoresResults = await client.zinterWithScores( + [key1, key2], + { aggregationType: "SUM" }, + ); + const expectedMapSum = { + one: 2.5, + two: 4.5, + }; + expect(zinterWithScoresResults).toEqual(expectedMapSum); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zinter with scores with weights and aggregation test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Intersection results are aggregated by the SUM score of elements with weights + const zinterWithScoresResults = await client.zinterWithScores( + [ + [key1, 3], + [key2, 2], + ], + { aggregationType: "SUM" }, + ); + const expectedMapSum = { + one: 6, + two: 11, + }; + expect(zinterWithScoresResults).toEqual(expectedMapSum); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zinter empty test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + + // Non existing key zinter + expect( + await client.zinter([key1, "{testKey}-non_existing_key"]), + ).toEqual([]); + + // Non existing key zinterWithScores + expect( + await client.zinterWithScores([ + key1, + "{testKey}-non_existing_key", + ]), + ).toEqual({}); + + // Empty list check zinter + await expect(client.zinter([])).rejects.toThrow(); + + // Empty list check zinterWithScores + await expect(client.zinterWithScores([])).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zunion basic test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + const resultZunion = await client.zunion([key1, key2]); + const expectedZunion = ["one", "two", "three"]; + + expect(resultZunion.sort()).toEqual(expectedZunion.sort()); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zunion with scores basic test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + const resultZunionWithScores = await client.zunionWithScores([ + key1, + key2, + ]); + const expectedZunionWithScores = { + one: 2.5, + two: 4.5, + three: 3.5, + }; + expect(resultZunionWithScores).toEqual( + expectedZunionWithScores, + ); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zunion with scores with max aggregation test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Union results are aggregated by the MAX score of elements + const zunionWithScoresResults = await client.zunionWithScores( + [key1, key2], + "MAX", + ); + const expectedMapMax = { + one: 1.5, + two: 2.5, + three: 3.5, + }; + expect(zunionWithScoresResults).toEqual(expectedMapMax); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zunion with scores with min aggregation test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Union results are aggregated by the MIN score of elements + const zunionWithScoresResults = await client.zunionWithScores( + [key1, key2], + "MIN", + ); + const expectedMapMin = { + one: 1.0, + two: 2.0, + three: 3.5, + }; + expect(zunionWithScoresResults).toEqual(expectedMapMin); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zunion with scores with sum aggregation test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Union results are aggregated by the SUM score of elements + const zunionWithScoresResults = await client.zunionWithScores( + [key1, key2], + "SUM", + ); + const expectedMapSum = { + one: 2.5, + two: 4.5, + three: 3.5, + }; + expect(zunionWithScoresResults).toEqual(expectedMapSum); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zunion with scores with weights and aggregation test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + const key2 = "{testKey}:2-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + const membersScores2 = { one: 1.5, two: 2.5, three: 3.5 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + expect(await client.zadd(key2, membersScores2)).toEqual(3); + + // Union results are aggregated by the SUM score of elements with weights + const zunionWithScoresResults = await client.zunionWithScores( + [ + [key1, 3], + [key2, 2], + ], + "SUM", + ); + const expectedMapSum = { + one: 6, + two: 11, + three: 7, + }; + expect(zunionWithScoresResults).toEqual(expectedMapSum); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zunion empty test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + const key1 = "{testKey}:1-" + uuidv4(); + + const membersScores1 = { one: 1.0, two: 2.0 }; + + expect(await client.zadd(key1, membersScores1)).toEqual(2); + + // Non existing key zunion + expect( + await client.zunion([key1, "{testKey}-non_existing_key"]), + ).toEqual(["one", "two"]); + + // Non existing key zunionWithScores + expect( + await client.zunionWithScores([ + key1, + "{testKey}-non_existing_key", + ]), + ).toEqual(membersScores1); + + // Empty list check zunion + await expect(client.zunion([])).rejects.toThrow(); + + // Empty list check zunionWithScores + await expect(client.zunionWithScores([])).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `type test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + expect(await client.set(key, "value")).toEqual("OK"); + expect(await client.type(key)).toEqual("string"); + expect(await client.del([key])).toEqual(1); + + expect(await client.lpush(key, ["value"])).toEqual(1); + expect(await client.type(Buffer.from(key))).toEqual("list"); + expect(await client.del([key])).toEqual(1); + + expect(await client.sadd(key, ["value"])).toEqual(1); + expect(await client.type(key)).toEqual("set"); + expect(await client.del([key])).toEqual(1); + + expect(await client.zadd(key, { member: 1.0 })).toEqual(1); + expect(await client.type(key)).toEqual("zset"); + expect(await client.del([key])).toEqual(1); + + expect(await client.hset(key, { field: "value" })).toEqual(1); + expect(await client.type(Buffer.from(key))).toEqual("hash"); + expect(await client.del([key])).toEqual(1); + + await client.xadd(key, [["field", "value"]]); + expect(await client.type(key)).toEqual("stream"); + expect(await client.del([key])).toEqual(1); + expect(await client.type(key)).toEqual("none"); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `echo test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const message = uuidv4(); + expect(await client.echo(message)).toEqual(message); + expect( + client instanceof GlideClient + ? await client.echo(message, Decoder.String) + : await client.echo(message, { + decoder: Decoder.String, + }), + ).toEqual(message); + expect( + client instanceof GlideClient + ? await client.echo(message, Decoder.Bytes) + : await client.echo(message, { + decoder: Decoder.Bytes, + }), + ).toEqual(Buffer.from(message)); + expect( + client instanceof GlideClient + ? await client.echo( + Buffer.from(message), + Decoder.String, + ) + : await client.echo(Buffer.from(message), { + decoder: Decoder.String, + }), + ).toEqual(message); + expect(await client.echo(Buffer.from(message))).toEqual( + message, + ); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `strlen test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key1Value = uuidv4(); + const key1ValueLength = key1Value.length; + expect(await client.set(key1, key1Value)).toEqual("OK"); + expect(await client.strlen(key1)).toEqual(key1ValueLength); + + expect(await client.strlen(Buffer.from("nonExistKey"))).toEqual( + 0, + ); + + const listName = "myList"; + const listKey1Value = uuidv4(); + const listKey2Value = uuidv4(); + + expect( + await client.lpush(listName, [ + listKey1Value, + listKey2Value, + ]), + ).toEqual(2); + // An error is returned when key holds a non-string value + await expect(client.strlen(listName)).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `lindex test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const listName = uuidv4(); + const encodedListName = Buffer.from(uuidv4()); + const listKey1Value = uuidv4(); + const listKey2Value = uuidv4(); + expect( + await client.lpush(listName, [ + listKey1Value, + listKey2Value, + ]), + ).toEqual(2); + expect( + await client.lpush(encodedListName, [ + Buffer.from(listKey1Value), + Buffer.from(listKey2Value), + ]), + ).toEqual(2); + expect(await client.lindex(listName, 0)).toEqual(listKey2Value); + expect(await client.lindex(listName, 1)).toEqual(listKey1Value); + expect(await client.lindex("notExsitingList", 1)).toEqual(null); + expect(await client.lindex(listName, 3)).toEqual(null); + expect(await client.lindex(listName, 0, Decoder.Bytes)).toEqual( + Buffer.from(listKey2Value), + ); + expect(await client.lindex(listName, 1, Decoder.Bytes)).toEqual( + Buffer.from(listKey1Value), + ); + expect(await client.lindex(encodedListName, 0)).toEqual( + listKey2Value, + ); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `linsert test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + const key2Encoded = Buffer.from(key2); + const stringKey = uuidv4(); + const nonExistingKey = uuidv4(); + + expect(await client.lpush(key1, ["4", "3", "2", "1"])).toEqual( + 4, + ); + expect( + await client.linsert( + key1, + InsertPosition.Before, + "2", + "1.5", + ), + ).toEqual(5); + expect( + await client.linsert( + key1, + InsertPosition.After, + "3", + "3.5", + ), + ).toEqual(6); + expect(await client.lrange(key1, 0, -1)).toEqual([ + "1", + "1.5", + "2", + "3", + "3.5", + "4", + ]); + + expect( + await client.linsert( + key1, + InsertPosition.Before, + "nonExistingPivot", + "4", + ), + ).toEqual(-1); + expect( + await client.linsert( + nonExistingKey, + InsertPosition.Before, + "pivot", + "elem", + ), + ).toEqual(0); + + // key, pivot and element as buffers + expect(await client.lpush(key2, ["4", "3", "2", "1"])).toEqual( + 4, + ); + expect( + await client.linsert( + key2Encoded, + InsertPosition.Before, + Buffer.from("2"), + Buffer.from("1.5"), + ), + ).toEqual(5); + expect( + await client.linsert( + key2Encoded, + InsertPosition.After, + Buffer.from("3"), + Buffer.from("3.5"), + ), + ).toEqual(6); + expect(await client.lrange(key2Encoded, 0, -1)).toEqual([ + "1", + "1.5", + "2", + "3", + "3.5", + "4", + ]); + + // key exists, but it is not a list + expect(await client.set(stringKey, "value")).toEqual("OK"); + await expect( + client.linsert(stringKey, InsertPosition.Before, "a", "b"), + ).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zpopmin test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const membersScores = { a: 1, b: 2, c: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + expect(await client.zpopmin(key)).toEqual({ a: 1.0 }); + + expect( + compareMaps(await client.zpopmin(key, 3), { + b: 2.0, + c: 3.0, + }), + ).toBe(true); + expect(await client.zpopmin(key)).toEqual({}); + expect(await client.set(key, "value")).toEqual("OK"); + await expect(client.zpopmin(key)).rejects.toThrow(); + expect(await client.zpopmin("notExsitingKey")).toEqual({}); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zpopmax test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const membersScores = { a: 1, b: 2, c: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + expect(await client.zpopmax(key)).toEqual({ c: 3.0 }); + + expect( + compareMaps(await client.zpopmax(key, 3), { + b: 2.0, + a: 1.0, + }), + ).toBe(true); + expect(await client.zpopmax(key)).toEqual({}); + expect(await client.set(key, "value")).toEqual("OK"); + await expect(client.zpopmax(key)).rejects.toThrow(); + expect(await client.zpopmax("notExsitingKey")).toEqual({}); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `bzpopmax test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const key3 = "{key}-3" + uuidv4(); + + expect(await client.zadd(key1, { a: 1.0, b: 1.5 })).toBe(2); + expect(await client.zadd(key2, { c: 2.0 })).toBe(1); + expect(await client.bzpopmax([key1, key2], 0.5)).toEqual([ + key1, + "b", + 1.5, + ]); + + // nothing popped out / key does not exist + expect( + await client.bzpopmax( + [key3], + cluster.checkIfServerVersionLessThan("6.0.0") + ? 1.0 + : 0.001, + ), + ).toBeNull(); + + // pops from the second key + expect( + await client.bzpopmax([key3, Buffer.from(key2)], 0.5), + ).toEqual([key2, "c", 2.0]); + // pop with decoder + expect( + await client.bzpopmax([key1], 0.5, { + decoder: Decoder.Bytes, + }), + ).toEqual([Buffer.from(key1), Buffer.from("a"), 1.0]); + + // key exists but holds non-ZSET value + expect(await client.set(key3, "bzpopmax")).toBe("OK"); + await expect(client.bzpopmax([key3], 0.5)).rejects.toThrow( + RequestError, + ); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `bzpopmin test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const key3 = "{key}-3" + uuidv4(); + + expect(await client.zadd(key1, { a: 1.0, b: 1.5 })).toBe(2); + expect(await client.zadd(key2, { c: 2.0 })).toBe(1); + expect(await client.bzpopmin([key1, key2], 0.5)).toEqual([ + key1, + "a", + 1.0, + ]); + + // nothing popped out / key does not exist + expect( + await client.bzpopmin( + [key3], + cluster.checkIfServerVersionLessThan("6.0.0") + ? 1.0 + : 0.001, + ), + ).toBeNull(); + + // pops from the second key + expect( + await client.bzpopmin([key3, Buffer.from(key2)], 0.5), + ).toEqual([key2, "c", 2.0]); + // pop with decoder + expect( + await client.bzpopmin([key1], 0.5, { + decoder: Decoder.Bytes, + }), + ).toEqual([Buffer.from(key1), Buffer.from("b"), 1.5]); + + // key exists but holds non-ZSET value + expect(await client.set(key3, "bzpopmin")).toBe("OK"); + await expect(client.bzpopmin([key3], 0.5)).rejects.toThrow( + RequestError, + ); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `Pttl test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + expect(await client.pttl(key)).toEqual(-2); + + expect(await client.set(key, "value")).toEqual("OK"); + expect(await client.pttl(key)).toEqual(-1); + + expect(await client.expire(key, 10)).toEqual(true); + let result = await client.pttl(Buffer.from(key)); + expect(result).toBeGreaterThan(0); + expect(result).toBeLessThanOrEqual(10000); + + expect( + await client.expireAt( + key, + Math.floor(Date.now() / 1000) + 20, + ), + ).toEqual(true); + result = await client.pttl(key); + expect(result).toBeGreaterThan(0); + expect(result).toBeLessThanOrEqual(20000); + + expect(await client.pexpireAt(key, Date.now() + 30000)).toEqual( + true, + ); + result = await client.pttl(key); + expect(result).toBeGreaterThan(0); + expect(result).toBeLessThanOrEqual(30000); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zremRangeByRank test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + expect(await client.zremRangeByRank(key, 2, 1)).toEqual(0); + expect(await client.zremRangeByRank(key, 0, 1)).toEqual(2); + expect(await client.zremRangeByRank(key, 0, 10)).toEqual(1); + expect( + await client.zremRangeByRank("nonExistingKey", 0, -1), + ).toEqual(0); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrank test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + const membersScores = { one: 1.5, two: 2, three: 3 }; + expect(await client.zadd(key1, membersScores)).toEqual(3); + expect(await client.zrank(key1, "one")).toEqual(0); + + if (!cluster.checkIfServerVersionLessThan("7.2.0")) { + expect(await client.zrankWithScore(key1, "one")).toEqual([ + 0, 1.5, + ]); + expect( + await client.zrankWithScore(key1, "nonExistingMember"), + ).toEqual(null); + expect( + await client.zrankWithScore("nonExistingKey", "member"), + ).toEqual(null); + } + + expect(await client.zrank(key1, "nonExistingMember")).toEqual( + null, + ); + expect(await client.zrank("nonExistingKey", "member")).toEqual( + null, + ); + + expect(await client.set(key2, "value")).toEqual("OK"); + await expect(client.zrank(key2, "member")).rejects.toThrow(); + }, protocol); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrevrank test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + const key = uuidv4(); + const nonSetKey = uuidv4(); + const membersScores = { one: 1.5, two: 2, three: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + expect(await client.zrevrank(key, "three")).toEqual(0); + + if (!cluster.checkIfServerVersionLessThan("7.2.0")) { + expect(await client.zrevrankWithScore(key, "one")).toEqual([ + 2, 1.5, + ]); + expect( + await client.zrevrankWithScore( + key, + "nonExistingMember", + ), + ).toBeNull(); + expect( + await client.zrevrankWithScore( + "nonExistingKey", + "member", + ), + ).toBeNull(); + } + + expect( + await client.zrevrank(key, "nonExistingMember"), + ).toBeNull(); + expect( + await client.zrevrank("nonExistingKey", "member"), + ).toBeNull(); + + // Key exists, but is not a sorted set + expect(await client.set(nonSetKey, "value")).toEqual("OK"); + await expect( + client.zrevrank(nonSetKey, "member"), + ).rejects.toThrow(); + }, protocol); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `test brpop test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + expect( + await client.rpush("brpop-test", ["foo", "bar", "baz"]), + ).toEqual(3); + // Test basic usage + expect(await client.brpop(["brpop-test"], 0.1)).toEqual([ + "brpop-test", + "baz", + ]); + // Test encoded value + expect( + await client.brpop(["brpop-test"], 0.1, Decoder.Bytes), + ).toEqual([Buffer.from("brpop-test"), Buffer.from("bar")]); + // Delete all values from list + expect(await client.del(["brpop-test"])).toEqual(1); + // Test null return when key doesn't exist + expect(await client.brpop(["brpop-test"], 0.1)).toEqual(null); + // key exists, but it is not a list + await client.set("foo", "bar"); + await expect(client.brpop(["foo"], 0.1)).rejects.toThrow(); + + // Same-slot requirement + if (client instanceof GlideClusterClient) { + try { + expect( + await client.brpop(["abc", "zxy", "lkn"], 0.1), + ).toThrow(); + } catch (e) { + expect((e as Error).message.toLowerCase()).toMatch( + "crossslot", + ); + } + } + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `test blpop test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + expect( + await client.rpush("blpop-test", ["foo", "bar", "baz"]), + ).toEqual(3); + // Test basic usage + expect(await client.blpop(["blpop-test"], 0.1)).toEqual([ + "blpop-test", + "foo", + ]); + // Test decoded value + expect( + await client.blpop(["blpop-test"], 0.1, Decoder.Bytes), + ).toEqual([Buffer.from("blpop-test"), Buffer.from("bar")]); + // Delete all values from list + expect(await client.del(["blpop-test"])).toEqual(1); + // Test null return when key doesn't exist + expect(await client.blpop(["blpop-test"], 0.1)).toEqual(null); + // key exists, but it is not a list + await client.set("foo", "bar"); + await expect(client.blpop(["foo"], 0.1)).rejects.toThrow(); + + // Same-slot requirement + if (client instanceof GlideClusterClient) { + try { + expect( + await client.blpop(["abc", "zxy", "lkn"], 0.1), + ).toThrow(); + } catch (e) { + expect((e as Error).message.toLowerCase()).toMatch( + "crossslot", + ); + } + } + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `persist test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + expect(await client.set(key, "foo")).toEqual("OK"); + expect(await client.persist(key)).toEqual(false); + + expect(await client.expire(key, 10)).toEqual(true); + expect(await client.persist(Buffer.from(key))).toEqual(true); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `streams add, trim, and len test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const nonExistingKey = uuidv4(); + const stringKey = uuidv4(); + const field1 = uuidv4(); + const field2 = uuidv4(); + + const nullResult = await client.xadd( + key, + [ + [field1, "foo"], + [field2, "bar"], + ], + { + makeStream: false, + }, + ); + expect(nullResult).toBeNull(); + + const timestamp1 = await client.xadd( + key, + [ + [field1, "foo1"], + [field2, "bar1"], + ], + { id: "0-1" }, + ); + expect(timestamp1).toEqual("0-1"); + expect( + await client.xadd(key, [ + [field1, "foo2"], + [field2, "bar2"], + ]), + ).not.toBeNull(); + expect(await client.xlen(key)).toEqual(2); + + // this will trim the first entry. + const id = await client.xadd( + key, + [ + [field1, "foo3"], + [field2, "bar3"], + ], + { + trim: { + method: "maxlen", + threshold: 2, + exact: true, + }, + }, + ); + expect(id).not.toBeNull(); + expect(await client.xlen(key)).toEqual(2); + + // this will trim the 2nd entry. + expect( + await client.xadd( + key, + [ + [field1, "foo4"], + [field2, "bar4"], + ], + { + trim: { + method: "minid", + threshold: id as string, + exact: true, + }, + }, + ), + ).not.toBeNull(); + expect(await client.xlen(key)).toEqual(2); + + expect( + await client.xtrim(key, { + method: "maxlen", + threshold: 1, + exact: true, + }), + ).toEqual(1); + expect(await client.xlen(key)).toEqual(1); + + expect( + await client.xtrim(key, { + method: "maxlen", + threshold: 0, + exact: true, + }), + ).toEqual(1); + // Unlike other Redis collection types, stream keys still exist even after removing all entries + expect(await client.exists([key])).toEqual(1); + expect(await client.xlen(key)).toEqual(0); + + expect( + await client.xtrim(nonExistingKey, { + method: "maxlen", + threshold: 1, + exact: true, + }), + ).toEqual(0); + expect(await client.xlen(nonExistingKey)).toEqual(0); + + // key exists, but it is not a stream + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.xtrim(stringKey, { + method: "maxlen", + threshold: 1, + exact: true, + }), + ).rejects.toThrow(); + await expect(client.xlen(stringKey)).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `xrange and xrevrange test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + const key = uuidv4(); + const nonExistingKey = uuidv4(); + const stringKey = uuidv4(); + const streamId1 = "0-1"; + const streamId2 = "0-2"; + const streamId3 = "0-3"; + + expect( + await client.xadd(key, [["f1", "v1"]], { id: streamId1 }), + ).toEqual(streamId1); + expect( + await client.xadd(key, [["f2", "v2"]], { id: streamId2 }), + ).toEqual(streamId2); + expect(await client.xlen(key)).toEqual(2); + + // get everything from the stream + expect( + await client.xrange( + key, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).toEqual({ + [streamId1]: [["f1", "v1"]], + [streamId2]: [["f2", "v2"]], + }); + + expect( + await client.xrevrange( + key, + InfBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + ), + ).toEqual({ + [streamId2]: [["f2", "v2"]], + [streamId1]: [["f1", "v1"]], + }); + + // returns empty mapping if + before - + expect( + await client.xrange( + key, + InfBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + ), + ).toEqual({}); + // rev search returns empty mapping if - before + + expect( + await client.xrevrange( + key, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).toEqual({}); + + expect( + await client.xadd(key, [["f3", "v3"]], { id: streamId3 }), + ).toEqual(streamId3); + + // get the newest entry + if (!cluster.checkIfServerVersionLessThan("6.2.0")) { + expect( + await client.xrange( + key, + { isInclusive: false, value: streamId2 }, + { value: "5" }, + 1, + ), + ).toEqual({ [streamId3]: [["f3", "v3"]] }); + + expect( + await client.xrevrange( + key, + { value: "5" }, + { isInclusive: false, value: streamId2 }, + 1, + ), + ).toEqual({ [streamId3]: [["f3", "v3"]] }); + } + + // xrange/xrevrange against an emptied stream + expect( + await client.xdel(key, [streamId1, streamId2, streamId3]), + ).toEqual(3); + expect( + await client.xrange( + key, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + 10, + ), + ).toEqual({}); + expect( + await client.xrevrange( + key, + InfBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + 10, + ), + ).toEqual({}); + + expect( + await client.xrange( + nonExistingKey, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).toEqual({}); + expect( + await client.xrevrange( + nonExistingKey, + InfBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + ), + ).toEqual({}); + + // count value < 1 returns null + expect( + await client.xrange( + key, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + 0, + ), + ).toEqual(null); + expect( + await client.xrange( + key, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + -1, + ), + ).toEqual(null); + expect( + await client.xrevrange( + key, + InfBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + 0, + ), + ).toEqual(null); + expect( + await client.xrevrange( + key, + InfBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + -1, + ), + ).toEqual(null); + + // key exists, but it is not a stream + expect(await client.set(stringKey, "foo")); + await expect( + client.xrange( + stringKey, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).rejects.toThrow(RequestError); + await expect( + client.xrevrange( + stringKey, + InfBoundary.PositiveInfinity, + InfBoundary.NegativeInfinity, + ), + ).rejects.toThrow(RequestError); + + // invalid start bound + await expect( + client.xrange( + key, + { value: "not_a_stream_id" }, + InfBoundary.PositiveInfinity, + ), + ).rejects.toThrow(RequestError); + await expect( + client.xrevrange(key, InfBoundary.PositiveInfinity, { + value: "not_a_stream_id", + }), + ).rejects.toThrow(RequestError); + + // invalid end bound + await expect( + client.xrange(key, InfBoundary.NegativeInfinity, { + value: "not_a_stream_id", + }), + ).rejects.toThrow(RequestError); + await expect( + client.xrevrange( + key, + { + value: "not_a_stream_id", + }, + InfBoundary.NegativeInfinity, + ), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zremRangeByLex test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const stringKey = uuidv4(); + const membersScores = { a: 1, b: 2, c: 3, d: 4 }; + expect(await client.zadd(key, membersScores)).toEqual(4); + + expect( + await client.zremRangeByLex( + key, + { value: "a", isInclusive: false }, + { value: "c" }, + ), + ).toEqual(2); + + expect( + await client.zremRangeByLex( + key, + { value: "d" }, + InfBoundary.PositiveInfinity, + ), + ).toEqual(1); + + // MinLex > MaxLex + expect( + await client.zremRangeByLex( + key, + { value: "a" }, + InfBoundary.NegativeInfinity, + ), + ).toEqual(0); + + expect( + await client.zremRangeByLex( + "nonExistingKey", + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).toEqual(0); + + // Key exists, but it is not a set + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.zremRangeByLex( + stringKey, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zremRangeByScore test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + + expect( + await client.zremRangeByScore( + key, + { value: 1, isInclusive: false }, + { value: 2 }, + ), + ).toEqual(1); + + expect( + await client.zremRangeByScore( + key, + { value: 1 }, + InfBoundary.NegativeInfinity, + ), + ).toEqual(0); + + expect( + await client.zremRangeByScore( + "nonExistingKey", + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).toEqual(0); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zlexcount test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const stringKey = uuidv4(); + const membersScores = { a: 1, b: 2, c: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + + // In range negative to positive infinity. + expect( + await client.zlexcount( + key, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).toEqual(3); + + // In range a (exclusive) to positive infinity + expect( + await client.zlexcount( + key, + { value: "a", isInclusive: false }, + InfBoundary.PositiveInfinity, + ), + ).toEqual(2); + + // In range negative infinity to c (inclusive) + expect( + await client.zlexcount(key, InfBoundary.NegativeInfinity, { + value: "c", + isInclusive: true, + }), + ).toEqual(3); + + // Incorrect range start > end + expect( + await client.zlexcount(key, InfBoundary.PositiveInfinity, { + value: "c", + isInclusive: true, + }), + ).toEqual(0); + + // Non-existing key + expect( + await client.zlexcount( + "non_existing_key", + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).toEqual(0); + + // Key exists, but it is not a set + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.zlexcount( + stringKey, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "time test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + // Take the time now, convert to 10 digits and subtract 1 second + const now = Math.floor(new Date().getTime() / 1000 - 1); + const result = (await client.time()) as [string, string]; + expect(result?.length).toEqual(2); + expect(Number(result?.at(0))).toBeGreaterThan(now); + // Test its not more than 1 second + expect(Number(result?.at(1))).toBeLessThan(1000000); + }, protocol); + }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `streams xread test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = "{xread}-1-" + uuidv4(); + const key2 = "{xread}-2-" + uuidv4(); + const key3 = "{xread}-3-" + uuidv4(); + const field1 = "foo"; + const field2 = "bar"; + const field3 = "barvaz"; + + const timestamp_1_1 = await client.xadd(key1, [ + [field1, "foo1"], + [field3, "barvaz1"], + ]); + expect(timestamp_1_1).not.toBeNull(); + const timestamp_2_1 = await client.xadd(key2, [ + [field2, "bar1"], + ]); + expect(timestamp_2_1).not.toBeNull(); + const timestamp_1_2 = await client.xadd(key1, [ + [field1, "foo2"], + ]); + const timestamp_2_2 = await client.xadd(key2, [ + [field2, "bar2"], + ]); + const timestamp_1_3 = await client.xadd(key1, [ + [field1, "foo3"], + [field3, "barvaz3"], + ]); + const timestamp_2_3 = await client.xadd(key2, [ + [field2, "bar3"], + ]); + + const result = await client.xread( + { + [key1]: timestamp_1_1 as string, + [key2]: timestamp_2_1 as string, + }, + { + block: 1, + }, + ); + + const expected = { + [key1]: { + [timestamp_1_2 as string]: [[field1, "foo2"]], + [timestamp_1_3 as string]: [ + [field1, "foo3"], + [field3, "barvaz3"], + ], + }, + [key2]: { + [timestamp_2_2 as string]: [["bar", "bar2"]], + [timestamp_2_3 as string]: [["bar", "bar3"]], + }, + }; + expect(result).toEqual(expected); + + // key does not exist + expect(await client.xread({ [key3]: "0-0" })).toBeNull(); + expect( + await client.xread({ + [key2]: timestamp_2_1 as string, + [key3]: "0-0", + }), + ).toEqual({ + [key2]: { + [timestamp_2_2 as string]: [["bar", "bar2"]], + [timestamp_2_3 as string]: [["bar", "bar3"]], + }, + }); + + // key is not a stream + expect(await client.set(key3, uuidv4())).toEqual("OK"); + await expect(client.xread({ [key3]: "0-0" })).rejects.toThrow( + RequestError, + ); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `xreadgroup test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = "{xreadgroup}-1-" + uuidv4(); + const key2 = "{xreadgroup}-2-" + uuidv4(); + const key3 = "{xreadgroup}-3-" + uuidv4(); + const group = uuidv4(); + const consumer = uuidv4(); + + // setup data & test binary parameters in XGROUP CREATE commands + expect( + await client.xgroupCreate( + Buffer.from(key1), + Buffer.from(group), + Buffer.from("0"), + { + mkStream: true, + }, + ), + ).toEqual("OK"); + + expect( + await client.xgroupCreateConsumer( + Buffer.from(key1), + Buffer.from(group), + Buffer.from(consumer), + ), + ).toBeTruthy(); + + const entry1 = (await client.xadd(key1, [ + ["a", "b"], + ])) as string; + const entry2 = (await client.xadd(key1, [ + ["c", "d"], + ])) as string; + + // read the entire stream for the consumer and mark messages as pending + expect( + await client.xreadgroup(group, consumer, { [key1]: ">" }), + ).toEqual({ + [key1]: { + [entry1]: [["a", "b"]], + [entry2]: [["c", "d"]], + }, + }); + + // delete one of the entries + expect(await client.xdel(key1, [entry1])).toEqual(1); + + // now xreadgroup returns one empty entry and one non-empty entry + expect( + await client.xreadgroup(group, consumer, { [key1]: "0" }), + ).toEqual({ + [key1]: { + [entry1]: null, + [entry2]: [["c", "d"]], + }, + }); + + // try to read new messages only + expect( + await client.xreadgroup(group, consumer, { [key1]: ">" }), + ).toBeNull(); + + // add a message and read it with ">" + const entry3 = (await client.xadd(key1, [ + ["e", "f"], + ])) as string; + expect( + await client.xreadgroup(group, consumer, { [key1]: ">" }), + ).toEqual({ + [key1]: { + [entry3]: [["e", "f"]], + }, + }); + + // add second key with a group and a consumer, but no messages + expect( + await client.xgroupCreate(key2, group, "0", { + mkStream: true, + }), + ).toEqual("OK"); + expect( + await client.xgroupCreateConsumer(key2, group, consumer), + ).toBeTruthy(); + + // read both keys + expect( + await client.xreadgroup(group, consumer, { + [key1]: "0", + [key2]: "0", + }), + ).toEqual({ + [key1]: { + [entry1]: null, + [entry2]: [["c", "d"]], + [entry3]: [["e", "f"]], + }, + [key2]: {}, + }); + + // error cases: + // key does not exist + await expect( + client.xreadgroup("_", "_", { [key3]: "0-0" }), + ).rejects.toThrow(RequestError); + // key is not a stream + expect(await client.set(key3, uuidv4())).toEqual("OK"); + await expect( + client.xreadgroup("_", "_", { [key3]: "0-0" }), + ).rejects.toThrow(RequestError); + expect(await client.del([key3])).toEqual(1); + // group and consumer don't exist + await client.xadd(key3, [["a", "b"]]); + await expect( + client.xreadgroup("_", "_", { [key3]: "0-0" }), + ).rejects.toThrow(RequestError); + // consumer don't exist + expect(await client.xgroupCreate(key3, group, "0-0")).toEqual( + "OK", + ); + expect( + await client.xreadgroup(group, "_", { [key3]: "0-0" }), + ).toEqual({ + [key3]: {}, + }); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `xinfo stream xinfosream test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const groupName = `group-${uuidv4()}`; + const consumerName = `consumer-${uuidv4()}`; + const streamId0_0 = "0-0"; + const streamId1_0 = "1-0"; + const streamId1_1 = "1-1"; + + // Setup: add stream entry, create consumer group and consumer, read from stream with consumer + expect( + await client.xadd( + key, + [ + ["a", "b"], + ["c", "d"], + ], + { id: streamId1_0 }, + ), + ).toEqual(streamId1_0); + + expect( + await client.xgroupCreate(key, groupName, streamId0_0), + ).toEqual("OK"); + + await client.xreadgroup(groupName, consumerName, { + [key]: ">", + }); + + // test xinfoStream base (non-full) case: + const result = (await client.xinfoStream(key)) as { + length: number; + "radix-tree-keys": number; + "radix-tree-nodes": number; + "last-generated-id": string; + "max-deleted-entry-id": string; + "entries-added": number; + "recorded-first-entry-id": string; + "first-entry": (string | number | string[])[]; + "last-entry": (string | number | string[])[]; + groups: number; + }; + + // verify result: + expect(result.length).toEqual(1); + const expectedFirstEntry = ["1-0", ["a", "b", "c", "d"]]; + expect(result["first-entry"]).toEqual(expectedFirstEntry); + expect(result["last-entry"]).toEqual(expectedFirstEntry); + expect(result.groups).toEqual(1); + + // Add one more entry + expect( + await client.xadd(key, [["foo", "bar"]], { + id: streamId1_1, + }), + ).toEqual(streamId1_1); + const fullResult = (await client.xinfoStream(key, 1)) as { + length: number; + "radix-tree-keys": number; + "radix-tree-nodes": number; + "last-generated-id": string; + "max-deleted-entry-id": string; + "entries-added": number; + "recorded-first-entry-id": string; + entries: (string | number | string[])[][]; + groups: [ + { + name: string; + "last-delivered-id": string; + "entries-read": number; + lag: number; + "pel-count": number; + pending: (string | number)[][]; + consumers: [ + { + name: string; + "seen-time": number; + "active-time": number; + "pel-count": number; + pending: (string | number)[][]; + }, + ]; + }, + ]; + }; + + // verify full result like: + // { + // length: 2, + // 'radix-tree-keys': 1, + // 'radix-tree-nodes': 2, + // 'last-generated-id': '1-1', + // 'max-deleted-entry-id': '0-0', + // 'entries-added': 2, + // 'recorded-first-entry-id': '1-0', + // entries: [ [ '1-0', ['a', 'b', ...] ] ], + // groups: [ { + // name: 'group', + // 'last-delivered-id': '1-0', + // 'entries-read': 1, + // lag: 1, + // 'pel-count': 1, + // pending: [ [ '1-0', 'consumer', 1722624726802, 1 ] ], + // consumers: [ { + // name: 'consumer', + // 'seen-time': 1722624726802, + // 'active-time': 1722624726802, + // 'pel-count': 1, + // pending: [ [ '1-0', 'consumer', 1722624726802, 1 ] ], + // } + // ] + // } + // ] + // } + expect(fullResult.length).toEqual(2); + expect(fullResult["recorded-first-entry-id"]).toEqual( + streamId1_0, + ); + + // Only the first entry will be returned since we passed count: 1 + expect(fullResult.entries).toEqual([expectedFirstEntry]); + + // compare groupName, consumerName, and pending messages from the full info result: + const fullResultGroups = fullResult.groups; + expect(fullResultGroups.length).toEqual(1); + expect(fullResultGroups[0]["name"]).toEqual(groupName); + + const pendingResult = fullResultGroups[0]["pending"]; + expect(pendingResult.length).toEqual(1); + expect(pendingResult[0][0]).toEqual(streamId1_0); + expect(pendingResult[0][1]).toEqual(consumerName); + + const consumersResult = fullResultGroups[0]["consumers"]; + expect(consumersResult.length).toEqual(1); + expect(consumersResult[0]["name"]).toEqual(consumerName); + + const consumerPendingResult = fullResultGroups[0]["pending"]; + expect(consumerPendingResult.length).toEqual(1); + expect(consumerPendingResult[0][0]).toEqual(streamId1_0); + expect(consumerPendingResult[0][1]).toEqual(consumerName); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `xinfo stream edge cases and failures test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = `{key}-1-${uuidv4()}`; + const stringKey = `{key}-2-${uuidv4()}`; + const nonExistentKey = `{key}-3-${uuidv4()}`; + const streamId1_0 = "1-0"; + + // Setup: create empty stream + expect( + await client.xadd(key, [["field", "value"]], { + id: streamId1_0, + }), + ).toEqual(streamId1_0); + expect(await client.xdel(key, [streamId1_0])).toEqual(1); + + // XINFO STREAM called against empty stream + const result = await client.xinfoStream(key); + expect(result["length"]).toEqual(0); + expect(result["first-entry"]).toEqual(null); + expect(result["last-entry"]).toEqual(null); + + // XINFO STREAM FULL called against empty stream. Negative count values are ignored. + const fullResult = await client.xinfoStream(key, -3); + expect(fullResult["length"]).toEqual(0); + expect(fullResult["entries"]).toEqual([]); + expect(fullResult["groups"]).toEqual([]); + + // Calling XINFO STREAM with a non-existing key raises an error + await expect( + client.xinfoStream(nonExistentKey), + ).rejects.toThrow(); + await expect( + client.xinfoStream(nonExistentKey, true), + ).rejects.toThrow(); + await expect( + client.xinfoStream(nonExistentKey, 2), + ).rejects.toThrow(); + + // Key exists, but it is not a stream + await client.set(stringKey, "boofar"); + await expect(client.xinfoStream(stringKey)).rejects.toThrow(); + await expect( + client.xinfoStream(stringKey, true), + ).rejects.toThrow(); + await expect( + client.xinfoStream(stringKey, 2), + ).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "rename test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + // Making sure both keys will be oart of the same slot + const key = uuidv4() + "{123}"; + const newKey = uuidv4() + "{123}"; + await client.set(key, "value"); + expect(await client.rename(key, newKey)).toEqual("OK"); + expect(await client.get(newKey)).toEqual("value"); + // If key doesn't exist it should throw, it also test that key has successfully been renamed + await expect(client.rename(key, newKey)).rejects.toThrow(); + // rename back + expect( + await client.rename(Buffer.from(newKey), Buffer.from(key)), + ).toEqual("OK"); + expect(await client.get(key)).toEqual("value"); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "renamenx test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = `{key}-1-${uuidv4()}`; + const key2 = `{key}-2-${uuidv4()}`; + const key3 = `{key}-3-${uuidv4()}`; + + // renamenx missing key + try { + expect(await client.renamenx(key1, key2)).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch("no such key"); + } + + // renamenx a string + await client.set(key1, "key1"); + await client.set(key3, "key3"); + // Test that renamenx can rename key1 to key2 (non-existing value) + expect(await client.renamenx(Buffer.from(key1), key2)).toEqual( + true, + ); + // sanity check + expect(await client.get(key2)).toEqual("key1"); + // Test that renamenx doesn't rename key2 to key3 (with an existing value) + expect(await client.renamenx(key2, Buffer.from(key3))).toEqual( + false, + ); + // sanity check + expect(await client.get(key3)).toEqual("key3"); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "dump and restore test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const key3 = "{key}-3" + uuidv4(); + const key4 = "{key}-4" + uuidv4(); + const key5 = "{key}-5" + uuidv4(); + const nonExistingkey = "{nonExistingkey}-" + uuidv4(); + const value = "orange"; + const valueEncode = Buffer.from(value); + + expect(await client.set(key1, value)).toEqual("OK"); + + // Dump non-existing key + expect(await client.dump(nonExistingkey)).toBeNull(); + + // Dump existing key + let data = (await client.dump(key1)) as Buffer; + expect(data).not.toBeNull(); + + // Restore to a new key without option + expect(await client.restore(key2, 0, data)).toEqual("OK"); + expect(await client.get(key2, Decoder.String)).toEqual(value); + expect(await client.get(key2, Decoder.Bytes)).toEqual( + valueEncode, + ); + + // Restore to an existing key + await expect(client.restore(key2, 0, data)).rejects.toThrow( + "BUSYKEY: Target key name already exists.", + ); + + // Restore with `REPLACE` and existing key holding different value + expect(await client.sadd(key3, ["a"])).toEqual(1); + expect( + await client.restore(key3, 0, data, { replace: true }), + ).toEqual("OK"); + + // Restore with `REPLACE` option + expect( + await client.restore(key2, 0, data, { replace: true }), + ).toEqual("OK"); + + // Restore with `REPLACE`, `ABSTTL`, and positive TTL + expect( + await client.restore(key2, 1000, data, { + replace: true, + absttl: true, + }), + ).toEqual("OK"); + + // Restore with `REPLACE`, `ABSTTL`, and negative TTL + await expect( + client.restore(key2, -10, data, { + replace: true, + absttl: true, + }), + ).rejects.toThrow("Invalid TTL value"); + + // Restore with REPLACE and positive idletime + expect( + await client.restore(key2, 0, data, { + replace: true, + idletime: 10, + }), + ).toEqual("OK"); + + // Restore with REPLACE and negative idletime + await expect( + client.restore(key2, 0, data, { + replace: true, + idletime: -10, + }), + ).rejects.toThrow("Invalid IDLETIME value"); + + // Restore with REPLACE and positive frequency + expect( + await client.restore(key2, 0, data, { + replace: true, + frequency: 10, + }), + ).toEqual("OK"); + + // Restore with REPLACE and negative frequency + await expect( + client.restore(key2, 0, data, { + replace: true, + frequency: -10, + }), + ).rejects.toThrow("Invalid FREQ value"); + + // Restore only uses IDLETIME or FREQ modifiers + // Error will be raised if both options are set + await expect( + client.restore(key2, 0, data, { + replace: true, + idletime: 10, + frequency: 10, + }), + ).rejects.toThrow("syntax error"); + + // Restore with checksumto error + await expect( + client.restore(key2, 0, valueEncode, { replace: true }), + ).rejects.toThrow("DUMP payload version or checksum are wrong"); + + // Transaction tests + let response = + client instanceof GlideClient + ? await client.exec( + new Transaction().dump(key1), + Decoder.Bytes, + ) + : await client.exec( + new ClusterTransaction().dump(key1), + { decoder: Decoder.Bytes }, + ); + expect(response?.[0]).not.toBeNull(); + data = response?.[0] as Buffer; + + // Restore with `String` exec decoder + response = + client instanceof GlideClient + ? await client.exec( + new Transaction() + .restore(key4, 0, data) + .get(key4), + Decoder.String, + ) + : await client.exec( + new ClusterTransaction() + .restore(key4, 0, data) + .get(key4), + { decoder: Decoder.String }, + ); + expect(response?.[0]).toEqual("OK"); + expect(response?.[1]).toEqual(value); + + // Restore with `Bytes` exec decoder + response = + client instanceof GlideClient + ? await client.exec( + new Transaction() + .restore(key5, 0, data) + .get(key5), + Decoder.Bytes, + ) + : await client.exec( + new ClusterTransaction() + .restore(key5, 0, data) + .get(key5), + { decoder: Decoder.Bytes }, + ); + expect(response?.[0]).toEqual("OK"); + expect(response?.[1]).toEqual(valueEncode); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "pfadd test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + expect(await client.pfadd(key, [])).toEqual(1); + expect(await client.pfadd(key, ["one", "two"])).toEqual(1); + expect( + await client.pfadd(Buffer.from(key), [Buffer.from("two")]), + ).toEqual(0); + expect(await client.pfadd(key, [])).toEqual(0); + + // key exists, but it is not a HyperLogLog + expect(await client.set("foo", "value")).toEqual("OK"); + await expect(client.pfadd("foo", [])).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "pfcount test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = `{key}-1-${uuidv4()}`; + const key2 = `{key}-2-${uuidv4()}`; + const key3 = `{key}-3-${uuidv4()}`; + const stringKey = `{key}-4-${uuidv4()}`; + const nonExistingKey = `{key}-5-${uuidv4()}`; + + expect(await client.pfadd(key1, ["a", "b", "c"])).toEqual(1); + expect(await client.pfadd(key2, ["b", "c", "d"])).toEqual(1); + expect(await client.pfcount([key1])).toEqual(3); + expect(await client.pfcount([Buffer.from(key2)])).toEqual(3); + expect(await client.pfcount([key1, key2])).toEqual(4); + expect( + await client.pfcount([key1, key2, nonExistingKey]), + ).toEqual(4); + + // empty HyperLogLog data set + expect(await client.pfadd(key3, [])).toEqual(1); + expect(await client.pfcount([key3])).toEqual(0); + + // invalid argument - key list must not be empty + try { + expect(await client.pfcount([])).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "ResponseError: wrong number of arguments", + ); + } + + // key exists, but it is not a HyperLogLog + expect(await client.set(stringKey, "value")).toEqual("OK"); + await expect(client.pfcount([stringKey])).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "pfmerget test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = `{key}-1-${uuidv4()}`; + const key2 = `{key}-2-${uuidv4()}`; + const key3 = `{key}-3-${uuidv4()}`; + const stringKey = `{key}-4-${uuidv4()}`; + const nonExistingKey = `{key}-5-${uuidv4()}`; + + expect(await client.pfadd(key1, ["a", "b", "c"])).toEqual(1); + expect(await client.pfadd(key2, ["b", "c", "d"])).toEqual(1); + + // merge into new HyperLogLog data set + expect( + await client.pfmerge(Buffer.from(key3), [key1, key2]), + ).toEqual("OK"); + expect(await client.pfcount([key3])).toEqual(4); + + // merge into existing HyperLogLog data set + expect(await client.pfmerge(key1, [Buffer.from(key2)])).toEqual( + "OK", + ); + expect(await client.pfcount([key1])).toEqual(4); + + // non-existing source key + expect( + await client.pfmerge(key2, [key1, nonExistingKey]), + ).toEqual("OK"); + expect(await client.pfcount([key2])).toEqual(4); + + // empty source key list + expect(await client.pfmerge(key1, [])).toEqual("OK"); + expect(await client.pfcount([key1])).toEqual(4); + + // source key exists, but it is not a HyperLogLog + await client.set(stringKey, "foo"); + await expect(client.pfmerge(key3, [stringKey])).rejects.toThrow( + RequestError, + ); + + // destination key exists, but it is not a HyperLogLog + await expect(client.pfmerge(stringKey, [key3])).rejects.toThrow( + RequestError, + ); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "setrange test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const nonStringKey = uuidv4(); + + // new key + expect(await client.setrange(key, 0, "Hello World")).toBe(11); + + // existing key + expect(await client.setrange(key, 6, "GLIDE")).toBe(11); + expect(await client.get(key)).toEqual("Hello GLIDE"); + + // offset > len + expect(await client.setrange(key, 15, "GLIDE")).toBe(20); + expect(await client.get(key)).toEqual( + "Hello GLIDE\0\0\0\0GLIDE", + ); + + // non-string key + expect(await client.lpush(nonStringKey, ["_"])).toBe(1); + await expect( + client.setrange(nonStringKey, 0, "_"), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "append test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + const key3 = uuidv4(); + const value = uuidv4(); + const valueEncoded = Buffer.from(value); + + // Append on non-existing string(similar to SET) + expect(await client.append(key1, value)).toBe(value.length); + expect(await client.append(key1, value)).toBe(value.length * 2); + expect(await client.get(key1)).toEqual(value.concat(value)); + + // key exists but holding the wrong kind of value + expect(await client.sadd(key2, ["a"])).toBe(1); + await expect(client.append(key2, "_")).rejects.toThrow( + RequestError, + ); + + // Key and value as buffers + expect(await client.append(key3, valueEncoded)).toBe( + value.length, + ); + expect(await client.append(key3, valueEncoded)).toBe( + valueEncoded.length * 2, + ); + expect(await client.get(key3, Decoder.Bytes)).toEqual( + Buffer.concat([valueEncoded, valueEncoded]), + ); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "wait test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const value1 = uuidv4(); + const value2 = uuidv4(); + + // assert that wait returns 0 under standalone and 1 under cluster mode. + expect(await client.set(key, value1)).toEqual("OK"); + + if (client instanceof GlideClusterClient) { + expect(await client.wait(1, 1000)).toBeGreaterThanOrEqual( + 1, + ); + } else { + expect(await client.wait(1, 1000)).toBeGreaterThanOrEqual( + 0, + ); + } + + // command should fail on a negative timeout value + await expect(client.wait(1, -1)).rejects.toThrow(RequestError); + + // ensure that command doesn't time out even if timeout > request timeout (250ms by default) + expect(await client.set(key, value2)).toEqual("OK"); + expect(await client.wait(100, 500)).toBeGreaterThanOrEqual(0); + }, protocol); + }, + config.timeout, + ); + + // Set command tests + + async function setWithExpiryOptions(client: BaseClient) { + const key = uuidv4(); + const value = uuidv4(); + const setResWithExpirySetMilli = await client.set(key, value, { + expiry: { + type: TimeUnit.Milliseconds, + count: 500, + }, + }); + expect(setResWithExpirySetMilli).toEqual("OK"); + const getWithExpirySetMilli = await client.get(key); + expect(getWithExpirySetMilli).toEqual(value); + + const setResWithExpirySec = await client.set(key, value, { + expiry: { + type: TimeUnit.Seconds, + count: 1, + }, + }); + expect(setResWithExpirySec).toEqual("OK"); + const getResWithExpirySec = await client.get(key); + expect(getResWithExpirySec).toEqual(value); + + const setWithUnixSec = await client.set(key, value, { + expiry: { + type: TimeUnit.UnixSeconds, + count: Math.floor(Date.now() / 1000) + 1, + }, + }); + expect(setWithUnixSec).toEqual("OK"); + const getWithUnixSec = await client.get(key); + expect(getWithUnixSec).toEqual(value); + + const setResWithExpiryKeep = await client.set(key, value, { + expiry: "keepExisting", + }); + expect(setResWithExpiryKeep).toEqual("OK"); + const getResWithExpiryKeep = await client.get(key); + expect(getResWithExpiryKeep).toEqual(value); + // wait for the key to expire base on the previous set + let sleep = new Promise((resolve) => setTimeout(resolve, 1000)); + await sleep; + const getResExpire = await client.get(key); + // key should have expired + expect(getResExpire).toEqual(null); + const setResWithExpiryWithUmilli = await client.set(key, value, { + expiry: { + type: TimeUnit.UnixMilliseconds, + count: Date.now() + 1000, + }, + }); + expect(setResWithExpiryWithUmilli).toEqual("OK"); + // wait for the key to expire + sleep = new Promise((resolve) => setTimeout(resolve, 1001)); + await sleep; + const getResWithExpiryWithUmilli = await client.get(key); + // key should have expired + expect(getResWithExpiryWithUmilli).toEqual(null); + } + + async function setWithOnlyIfExistOptions(client: BaseClient) { + const key = uuidv4(); + const value = uuidv4(); + const setKey = await client.set(key, value); + expect(setKey).toEqual("OK"); + const getRes = await client.get(key); + expect(getRes).toEqual(value); + const setExistingKeyRes = await client.set(key, value, { + conditionalSet: "onlyIfExists", + }); + expect(setExistingKeyRes).toEqual("OK"); + const getExistingKeyRes = await client.get(key); + expect(getExistingKeyRes).toEqual(value); + + const notExistingKeyRes = await client.set(key + 1, value, { + conditionalSet: "onlyIfExists", + }); + // key does not exist, so it should not be set + expect(notExistingKeyRes).toEqual(null); + const getNotExistingKey = await client.get(key + 1); + // key should not have been set + expect(getNotExistingKey).toEqual(null); + } + + async function setWithOnlyIfNotExistOptions(client: BaseClient) { + const key = uuidv4(); + const value = uuidv4(); + const notExistingKeyRes = await client.set(key, value, { + conditionalSet: "onlyIfDoesNotExist", + }); + // key does not exist, so it should be set + expect(notExistingKeyRes).toEqual("OK"); + const getNotExistingKey = await client.get(key); + // key should have been set + expect(getNotExistingKey).toEqual(value); + + const existingKeyRes = await client.set(key, value, { + conditionalSet: "onlyIfDoesNotExist", + }); + // key exists, so it should not be set + expect(existingKeyRes).toEqual(null); + const getExistingKey = await client.get(key); + // key should not have been set + expect(getExistingKey).toEqual(value); + } + + async function setWithGetOldOptions(client: BaseClient) { + const key = uuidv4(); + const value = uuidv4(); + + const setResGetNotExistOld = await client.set(key, value, { + returnOldValue: true, + }); + // key does not exist, so old value should be null + expect(setResGetNotExistOld).toEqual(null); + // key should have been set + const getResGetNotExistOld = await client.get(key); + expect(getResGetNotExistOld).toEqual(value); + + const setResGetExistOld = await client.set(key, value, { + returnOldValue: true, + }); + // key exists, so old value should be returned + expect(setResGetExistOld).toEqual(value); + // key should have been set + const getResGetExistOld = await client.get(key); + expect(getResGetExistOld).toEqual(value); + } + + async function setWithAllOptions(client: BaseClient) { + const key = uuidv4(); + const value = uuidv4(); + + // set with multiple options: + // * only apply SET if the key already exists + // * expires after 1 second + // * returns the old value + const setResWithAllOptions = await client.set(key, value, { + expiry: { + type: TimeUnit.UnixSeconds, + count: Math.floor(Date.now() / 1000) + 1, + }, + conditionalSet: "onlyIfExists", + returnOldValue: true, + }); + // key does not exist, so old value should be null + expect(setResWithAllOptions).toEqual(null); + // key does not exist, so SET should not have applied + expect(await client.get(key)).toEqual(null); + } + + async function testSetWithAllCombination(client: BaseClient) { + const key = uuidv4(); + const value = uuidv4(); + const count = 2; + const expiryCombination = [ + { type: TimeUnit.Seconds, count }, + { type: TimeUnit.Milliseconds, count }, + { type: TimeUnit.UnixSeconds, count }, + { type: TimeUnit.UnixMilliseconds, count }, + "keepExisting", + ]; + let exist = false; + + for (const expiryVal of expiryCombination) { + const setRes = await client.set(key, value, { + expiry: expiryVal as + | "keepExisting" + | { + type: + | TimeUnit.Seconds + | TimeUnit.Milliseconds + | TimeUnit.UnixSeconds + | TimeUnit.UnixMilliseconds; + count: number; + }, + conditionalSet: "onlyIfDoesNotExist", + }); + + if (exist == false) { + expect(setRes).toEqual("OK"); + exist = true; + } else { + expect(setRes).toEqual(null); + } + + const getRes = await client.get(key); + expect(getRes).toEqual(value); + } + + for (const expiryVal of expiryCombination) { + const setRes = await client.set(key, value, { + expiry: expiryVal as + | "keepExisting" + | { + type: + | TimeUnit.Seconds + | TimeUnit.Milliseconds + | TimeUnit.UnixSeconds + | TimeUnit.UnixMilliseconds; + count: number; + }, + + conditionalSet: "onlyIfExists", + returnOldValue: true, + }); + + expect(setRes).toBeDefined(); + } + } + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "Set commands with options test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + await setWithExpiryOptions(client); + await setWithOnlyIfExistOptions(client); + await setWithOnlyIfNotExistOptions(client); + await setWithGetOldOptions(client); + await setWithAllOptions(client); + await testSetWithAllCombination(client); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "object encoding test_%p", + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + const string_key = uuidv4(); + const list_key = uuidv4(); + const hashtable_key = uuidv4(); + const intset_key = uuidv4(); + const set_listpack_key = uuidv4(); + const hash_hashtable_key = uuidv4(); + const hash_listpack_key = uuidv4(); + const skiplist_key = uuidv4(); + const zset_listpack_key = uuidv4(); + const stream_key = uuidv4(); + const non_existing_key = uuidv4(); + const versionLessThan7 = + cluster.checkIfServerVersionLessThan("7.0.0"); + const versionLessThan72 = + cluster.checkIfServerVersionLessThan("7.2.0"); + + expect(await client.objectEncoding(non_existing_key)).toEqual( + null, + ); + + expect( + await client.set( + string_key, + "a really loooooooooooooooooooooooooooooooooooooooong value", + ), + ).toEqual("OK"); + + expect(await client.objectEncoding(string_key)).toEqual("raw"); + + expect(await client.set(string_key, "2")).toEqual("OK"); + expect( + await client.objectEncoding(Buffer.from(string_key)), + ).toEqual("int"); + + expect(await client.set(string_key, "value")).toEqual("OK"); + expect(await client.objectEncoding(string_key)).toEqual( + "embstr", + ); + + expect(await client.lpush(list_key, ["1"])).toEqual(1); + + if (versionLessThan72) { + expect(await client.objectEncoding(list_key)).toEqual( + "quicklist", + ); + } else { + expect(await client.objectEncoding(list_key)).toEqual( + "listpack", + ); + } + + // The default value of set-max-intset-entries is 512 + for (let i = 0; i < 513; i++) { + expect( + await client.sadd(hashtable_key, [String(i)]), + ).toEqual(1); + } + + expect(await client.objectEncoding(hashtable_key)).toEqual( + "hashtable", + ); + + expect(await client.sadd(intset_key, ["1"])).toEqual(1); + expect(await client.objectEncoding(intset_key)).toEqual( + "intset", + ); + + expect(await client.sadd(set_listpack_key, ["foo"])).toEqual(1); + + if (versionLessThan72) { + expect( + await client.objectEncoding(set_listpack_key), + ).toEqual("hashtable"); + } else { + expect( + await client.objectEncoding(set_listpack_key), + ).toEqual("listpack"); + } + + // The default value of hash-max-listpack-entries is 512 + for (let i = 0; i < 513; i++) { + expect( + await client.hset(hash_hashtable_key, { + [String(i)]: "2", + }), + ).toEqual(1); + } + + expect(await client.objectEncoding(hash_hashtable_key)).toEqual( + "hashtable", + ); + + expect( + await client.hset(hash_listpack_key, { "1": "2" }), + ).toEqual(1); + + if (versionLessThan7) { + expect( + await client.objectEncoding(hash_listpack_key), + ).toEqual("ziplist"); + } else { + expect( + await client.objectEncoding(hash_listpack_key), + ).toEqual("listpack"); + } + + // The default value of zset-max-listpack-entries is 128 + for (let i = 0; i < 129; i++) { + expect( + await client.zadd(skiplist_key, { [String(i)]: 2.0 }), + ).toEqual(1); + } + + expect(await client.objectEncoding(skiplist_key)).toEqual( + "skiplist", + ); + + expect( + await client.zadd(zset_listpack_key, { "1": 2.0 }), + ).toEqual(1); + + if (versionLessThan7) { + expect( + await client.objectEncoding( + Buffer.from(zset_listpack_key), + ), + ).toEqual("ziplist"); + } else { + expect( + await client.objectEncoding(zset_listpack_key), + ).toEqual("listpack"); + } + + expect( + await client.xadd(stream_key, [["field", "value"]]), + ).not.toBeNull(); + expect(await client.objectEncoding(stream_key)).toEqual( + "stream", + ); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "object freq test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const nonExistingKey = uuidv4(); + const maxmemoryPolicyKey = "maxmemory-policy"; + const config = await client.configGet([maxmemoryPolicyKey]); + const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); + + try { + expect( + await client.configSet({ + [maxmemoryPolicyKey]: "allkeys-lfu", + }), + ).toEqual("OK"); + expect(await client.objectFreq(nonExistingKey)).toEqual( + null, + ); + expect(await client.set(key, "foobar")).toEqual("OK"); + expect( + await client.objectFreq(Buffer.from(key)), + ).toBeGreaterThanOrEqual(0); + } finally { + expect( + await client.configSet({ + [maxmemoryPolicyKey]: maxmemoryPolicy, + }), + ).toEqual("OK"); + } + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "object idletime test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const nonExistingKey = uuidv4(); + const maxmemoryPolicyKey = "maxmemory-policy"; + const config = await client.configGet([maxmemoryPolicyKey]); + const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); + + try { + expect( + await client.configSet({ + // OBJECT IDLETIME requires a non-LFU maxmemory-policy + [maxmemoryPolicyKey]: "allkeys-random", + }), + ).toEqual("OK"); + expect(await client.objectIdletime(nonExistingKey)).toEqual( + null, + ); + expect(await client.set(key, "foobar")).toEqual("OK"); + + await wait(2000); + + expect( + await client.objectIdletime(Buffer.from(key)), + ).toBeGreaterThan(0); + } finally { + expect( + await client.configSet({ + [maxmemoryPolicyKey]: maxmemoryPolicy, + }), + ).toEqual("OK"); + } + }, protocol); + }, + config.timeout, + ); + + function wait(numMilliseconds: number) { + return new Promise((resolve) => { + setTimeout(resolve, numMilliseconds); + }); + } + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `object refcount test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = `{key}:${uuidv4()}`; + const nonExistingKey = `{key}:${uuidv4()}`; + + expect(await client.objectRefcount(nonExistingKey)).toBeNull(); + expect(await client.set(key, "foo")).toEqual("OK"); + expect( + await client.objectRefcount(Buffer.from(key)), + ).toBeGreaterThanOrEqual(1); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `flushall test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + // Test FLUSHALL SYNC + expect(await client.flushall(FlushMode.SYNC)).toBe("OK"); + + // TODO: replace with KEYS command when implemented + const keysAfter = (await client.customCommand([ + "keys", + "*", + ])) as string[]; + expect(keysAfter.length).toBe(0); + + // Test various FLUSHALL calls + expect(await client.flushall()).toBe("OK"); + expect(await client.flushall(FlushMode.ASYNC)).toBe("OK"); + + if (client instanceof GlideClusterClient) { + const key = uuidv4(); + const primaryRoute: SingleNodeRoute = { + type: "primarySlotKey", + key: key, + }; + expect(await client.flushall({ route: primaryRoute })).toBe( + "OK", + ); + expect( + await client.flushall({ + mode: FlushMode.ASYNC, + route: primaryRoute, + }), + ).toBe("OK"); + + //Test FLUSHALL on replica (should fail) + const key2 = uuidv4(); + const replicaRoute: SingleNodeRoute = { + type: "replicaSlotKey", + key: key2, + }; + await expect( + client.flushall({ route: replicaRoute }), + ).rejects.toThrowError(); + } + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `lpos test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = `{key}:${uuidv4()}`; + const valueArray = ["a", "a", "b", "c", "a", "b"]; + expect(await client.rpush(key, valueArray)).toEqual(6); + + // simplest case + expect(await client.lpos(key, "a")).toEqual(0); + expect(await client.lpos(key, "b", { rank: 2 })).toEqual(5); + + // element doesn't exist + expect(await client.lpos(key, "e")).toBeNull(); + + // reverse traversal + expect(await client.lpos(key, "b", { rank: -2 })).toEqual(2); + + // reverse traversal with binary key and element. + expect( + await client.lpos(Buffer.from(key), Buffer.from("b"), { + rank: -2, + }), + ).toEqual(2); + + // unlimited comparisons + expect( + await client.lpos(key, "a", { rank: 1, maxLength: 0 }), + ).toEqual(0); + + // limited comparisons + expect( + await client.lpos(key, "c", { rank: 1, maxLength: 2 }), + ).toBeNull(); + + // invalid rank value + await expect( + client.lpos(key, "a", { rank: 0 }), + ).rejects.toThrow(RequestError); + + // invalid maxlen value + await expect( + client.lpos(key, "a", { maxLength: -1 }), + ).rejects.toThrow(RequestError); + + // non-existent key + expect(await client.lpos("non-existent_key", "e")).toBeNull(); + + // wrong key data type + const wrongDataType = `{key}:${uuidv4()}`; + expect(await client.sadd(wrongDataType, ["a", "b"])).toEqual(2); + + await expect(client.lpos(wrongDataType, "a")).rejects.toThrow( + RequestError, + ); + + // invalid count value + await expect( + client.lpos(key, "a", { count: -1 }), + ).rejects.toThrow(RequestError); + + // with count + expect(await client.lpos(key, "a", { count: 2 })).toEqual([ + 0, 1, + ]); + expect(await client.lpos(key, "a", { count: 0 })).toEqual([ + 0, 1, 4, + ]); + expect( + await client.lpos(key, "a", { rank: 1, count: 0 }), + ).toEqual([0, 1, 4]); + expect( + await client.lpos(key, "a", { rank: 2, count: 0 }), + ).toEqual([1, 4]); + expect( + await client.lpos(key, "a", { rank: 3, count: 0 }), + ).toEqual([4]); + + // reverse traversal + expect( + await client.lpos(key, "a", { rank: -1, count: 0 }), + ).toEqual([4, 1, 0]); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `dbsize test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + // flush all data + expect(await client.flushall()).toBe("OK"); + + // check that DBSize is 0 + expect(await client.dbsize()).toBe(0); + + // set 10 random key-value pairs + for (let i = 0; i < 10; i++) { + const key = `{key}:${uuidv4()}`; + const value = "0".repeat(Math.random() * 7); + + expect(await client.set(key, value)).toBe("OK"); + } + + // check DBSIZE after setting + expect(await client.dbsize()).toBe(10); + + // additional test for the standalone client + if (client instanceof GlideClient) { + expect(await client.flushall()).toBe("OK"); + const key = uuidv4(); + expect(await client.set(key, "value")).toBe("OK"); + expect(await client.dbsize()).toBe(1); + // switching to another db to check size + expect(await client.select(1)).toBe("OK"); + expect(await client.dbsize()).toBe(0); + } + + // additional test for the cluster client + if (client instanceof GlideClusterClient) { + expect(await client.flushall()).toBe("OK"); + const key = uuidv4(); + expect(await client.set(key, "value")).toBe("OK"); + const primaryRoute: SingleNodeRoute = { + type: "primarySlotKey", + key: key, + }; + expect(await client.dbsize({ route: primaryRoute })).toBe( + 1, + ); + } + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `bitcount test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + const value = "foobar"; + + expect(await client.set(key1, value)).toEqual("OK"); + expect(await client.bitcount(key1)).toEqual(26); + expect( + await client.bitcount(Buffer.from(key1), { + start: 1, + end: 1, + }), + ).toEqual(6); + expect( + await client.bitcount(key1, { start: 0, end: -5 }), + ).toEqual(10); + // non-existing key + expect(await client.bitcount(uuidv4())).toEqual(0); + expect( + await client.bitcount(uuidv4(), { start: 5, end: 30 }), + ).toEqual(0); + // key exists, but it is not a string + expect(await client.sadd(key2, [value])).toEqual(1); + await expect(client.bitcount(key2)).rejects.toThrow( + RequestError, + ); + await expect( + client.bitcount(key2, { start: 1, end: 1 }), + ).rejects.toThrow(RequestError); + + if (cluster.checkIfServerVersionLessThan("7.0.0")) { + await expect( + client.bitcount(key1, { + start: 2, + end: 5, + indexType: BitmapIndexType.BIT, + }), + ).rejects.toThrow(); + await expect( + client.bitcount(key1, { + start: 2, + end: 5, + indexType: BitmapIndexType.BYTE, + }), + ).rejects.toThrow(); + } else { + expect( + await client.bitcount(key1, { + start: 2, + end: 5, + indexType: BitmapIndexType.BYTE, + }), + ).toEqual(16); + expect( + await client.bitcount(key1, { + start: 5, + end: 30, + indexType: BitmapIndexType.BIT, + }), + ).toEqual(17); + expect( + await client.bitcount(key1, { + start: 5, + end: -5, + indexType: BitmapIndexType.BIT, + }), + ).toEqual(23); + expect( + await client.bitcount(uuidv4(), { + start: 2, + end: 5, + indexType: BitmapIndexType.BYTE, + }), + ).toEqual(0); + // key exists, but it is not a string + await expect( + client.bitcount(key2, { + start: 1, + end: 1, + indexType: BitmapIndexType.BYTE, + }), + ).rejects.toThrow(RequestError); + } + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `geoadd geopos test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + const membersToCoordinates = new Map(); + membersToCoordinates.set("Palermo", { + longitude: 13.361389, + latitude: 38.115556, + }); + membersToCoordinates.set("Catania", { + longitude: 15.087269, + latitude: 37.502669, + }); + + // default geoadd + expect(await client.geoadd(key1, membersToCoordinates)).toBe(2); + + let geopos = await client.geopos(key1, [ + "Palermo", + "Catania", + "New York", + ]); + // inner array is possibly null, we need a null check or a cast + expect(geopos[0]?.[0]).toBeCloseTo(13.361389, 5); + expect(geopos[0]?.[1]).toBeCloseTo(38.115556, 5); + expect(geopos[1]?.[0]).toBeCloseTo(15.087269, 5); + expect(geopos[1]?.[1]).toBeCloseTo(37.502669, 5); + expect(geopos[2]).toBeNull(); + + // empty array of places + geopos = await client.geopos(key1, []); + expect(geopos).toEqual([]); + + // not existing key + geopos = await client.geopos(key2, []); + expect(geopos).toEqual([]); + geopos = await client.geopos(key2, ["Palermo"]); + expect(geopos).toEqual([null]); + + // with update mode options + membersToCoordinates.set("Catania", { + longitude: 15.087269, + latitude: 39, + }); + expect( + await client.geoadd(key1, membersToCoordinates, { + updateMode: ConditionalChange.ONLY_IF_DOES_NOT_EXIST, + }), + ).toBe(0); + expect( + await client.geoadd(key1, membersToCoordinates, { + updateMode: ConditionalChange.ONLY_IF_EXISTS, + }), + ).toBe(0); + + // with changed option + membersToCoordinates.set("Catania", { + longitude: 15.087269, + latitude: 40, + }); + membersToCoordinates.set("Tel-Aviv", { + longitude: 32.0853, + latitude: 34.7818, + }); + expect( + await client.geoadd(key1, membersToCoordinates, { + changed: true, + }), + ).toBe(2); + + // key exists but holding non-zset value + expect(await client.set(key2, "foo")).toBe("OK"); + await expect( + client.geoadd(key2, membersToCoordinates), + ).rejects.toThrow(); + await expect(client.geopos(key2, ["*_*"])).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `geoadd invalid args test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + + // empty coordinate map + await expect(client.geoadd(key, new Map())).rejects.toThrow(); + + // coordinate out of bound + await expect( + client.geoadd( + key, + new Map([["Place", { longitude: -181, latitude: 0 }]]), + ), + ).rejects.toThrow(); + await expect( + client.geoadd( + key, + new Map([["Place", { longitude: 181, latitude: 0 }]]), + ), + ).rejects.toThrow(); + await expect( + client.geoadd( + key, + new Map([["Place", { longitude: 0, latitude: 86 }]]), + ), + ).rejects.toThrow(); + await expect( + client.geoadd( + key, + new Map([["Place", { longitude: 0, latitude: -86 }]]), + ), + ).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `geosearch geosearchstore test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) return; + + const key1 = "{geosearch}" + uuidv4(); + const key2 = "{geosearch}" + uuidv4(); + const key3 = "{geosearch}" + uuidv4(); + + const members: string[] = [ + "Catania", + "Palermo", + "edge2", + "edge1", + ]; + const membersSet: Set = new Set(members); + const membersCoordinates: [number, number][] = [ + [15.087269, 37.502669], + [13.361389, 38.115556], + [17.24151, 38.788135], + [12.758489, 38.788135], + ]; + + const membersGeoData: GeospatialData[] = []; + + for (const [lon, lat] of membersCoordinates) { + membersGeoData.push({ longitude: lon, latitude: lat }); + } + + const membersToCoordinates = new Map(); + + for (let i = 0; i < members.length; i++) { + membersToCoordinates.set(members[i], membersGeoData[i]); + } + + const expectedResult = [ + [ + members[0], + [ + 56.4413, + 3479447370796909, + [15.087267458438873, 37.50266842333162], + ], + ], + [ + members[1], + [ + 190.4424, + 3479099956230698, + [13.361389338970184, 38.1155563954963], + ], + ], + [ + members[2], + [ + 279.7403, + 3481342659049484, + [17.241510450839996, 38.78813451624225], + ], + ], + [ + members[3], + [ + 279.7405, + 3479273021651468, + [12.75848776102066, 38.78813451624225], + ], + ], + ]; + + // geoadd + expect(await client.geoadd(key1, membersToCoordinates)).toBe( + members.length, + ); + + let searchResult = await client.geosearch( + key1, + { position: { longitude: 15, latitude: 37 } }, + { width: 400, height: 400, unit: GeoUnit.KILOMETERS }, + ); + // using set to compare, because results are reordrered + expect(new Set(searchResult)).toEqual(membersSet); + // same with geosearchstore + expect( + await client.geosearchstore( + key2, + key1, + { position: { longitude: 15, latitude: 37 } }, + { width: 400, height: 400, unit: GeoUnit.KILOMETERS }, + ), + ).toEqual(4); + expect( + await client.zrange(key2, { start: 0, stop: -1 }), + ).toEqual(searchResult); + + // order search result + searchResult = await client.geosearch( + key1, + { position: { longitude: 15, latitude: 37 } }, + { width: 400, height: 400, unit: GeoUnit.KILOMETERS }, + { sortOrder: SortOrder.ASC }, + ); + expect(searchResult).toEqual(members); + // same with geosearchstore + expect( + await client.geosearchstore( + key2, + key1, + { position: { longitude: 15, latitude: 37 } }, + { width: 400, height: 400, unit: GeoUnit.KILOMETERS }, + { sortOrder: SortOrder.ASC, storeDist: true }, + ), + ).toEqual(4); + expect( + await client.zrange(key2, { start: 0, stop: -1 }), + ).toEqual(searchResult); + + // order and query all extra data + searchResult = await client.geosearch( + key1, + { position: { longitude: 15, latitude: 37 } }, + { width: 400, height: 400, unit: GeoUnit.KILOMETERS }, + { + sortOrder: SortOrder.ASC, + withCoord: true, + withDist: true, + withHash: true, + }, + ); + expect(searchResult).toEqual(expectedResult); + + // order, query and limit by 1 + searchResult = await client.geosearch( + key1, + { position: { longitude: 15, latitude: 37 } }, + { width: 400, height: 400, unit: GeoUnit.KILOMETERS }, + { + sortOrder: SortOrder.ASC, + withCoord: true, + withDist: true, + withHash: true, + count: 1, + }, + ); + expect(searchResult).toEqual(expectedResult.slice(0, 1)); + // same with geosearchstore + expect( + await client.geosearchstore( + key2, + key1, + { position: { longitude: 15, latitude: 37 } }, + { width: 400, height: 400, unit: GeoUnit.KILOMETERS }, + { + sortOrder: SortOrder.ASC, + count: 1, + storeDist: true, + }, + ), + ).toEqual(1); + expect( + await client.zrange(key2, { start: 0, stop: -1 }), + ).toEqual([members[0]]); + + // test search by box, unit: meters, from member, with distance + const meters = 400 * 1000; + searchResult = await client.geosearch( + key1, + { member: "Catania" }, + { width: meters, height: meters, unit: GeoUnit.METERS }, + { + withDist: true, + withCoord: false, + sortOrder: SortOrder.DESC, + }, + ); + expect(searchResult).toEqual([ + ["edge2", [236529.1799]], + ["Palermo", [166274.1516]], + ["Catania", [0.0]], + ]); + // same with geosearchstore + expect( + await client.geosearchstore( + key2, + key1, + { member: "Catania" }, + { width: meters, height: meters, unit: GeoUnit.METERS }, + { sortOrder: SortOrder.DESC, storeDist: true }, + ), + ).toEqual(3); + // TODO deep close to https://github.com/maasencioh/jest-matcher-deep-close-to + expect( + await client.zrangeWithScores( + key2, + { start: 0, stop: -1 }, + true, + ), + ).toEqual({ + edge2: 236529.17986494553, + Palermo: 166274.15156960033, + Catania: 0.0, + }); + + // test search by box, unit: feet, from member, with limited count 2, with hash + const feet = 400 * 3280.8399; + searchResult = await client.geosearch( + key1, + { member: "Palermo" }, + { width: feet, height: feet, unit: GeoUnit.FEET }, + { + withDist: false, + withCoord: false, + withHash: true, + sortOrder: SortOrder.ASC, + count: 2, + }, + ); + expect(searchResult).toEqual([ + ["Palermo", [3479099956230698]], + ["edge1", [3479273021651468]], + ]); + // same with geosearchstore + expect( + await client.geosearchstore( + key2, + key1, + { member: "Palermo" }, + { width: feet, height: feet, unit: GeoUnit.FEET }, + { + sortOrder: SortOrder.ASC, + count: 2, + }, + ), + ).toEqual(2); + expect( + await client.zrangeWithScores(key2, { start: 0, stop: -1 }), + ).toEqual({ + Palermo: 3479099956230698, + edge1: 3479273021651468, + }); + + // test search by box, unit: miles, from geospatial position, with limited ANY count to 1 + const miles = 250; + searchResult = await client.geosearch( + key1, + { position: { longitude: 15, latitude: 37 } }, + { width: miles, height: miles, unit: GeoUnit.MILES }, + { count: 1, isAny: true }, + ); + expect(members).toContainEqual(searchResult[0]); + // same with geosearchstore + expect( + await client.geosearchstore( + key2, + key1, + { position: { longitude: 15, latitude: 37 } }, + { width: miles, height: miles, unit: GeoUnit.MILES }, + { count: 1, isAny: true }, + ), + ).toEqual(1); + expect( + await client.zrange(key2, { start: 0, stop: -1 }), + ).toEqual(searchResult); + + // test search by radius, units: feet, from member + const feetRadius = 200 * 3280.8399; + searchResult = await client.geosearch( + key1, + { member: "Catania" }, + { radius: feetRadius, unit: GeoUnit.FEET }, + { sortOrder: SortOrder.ASC }, + ); + expect(searchResult).toEqual(["Catania", "Palermo"]); + // same with geosearchstore + expect( + await client.geosearchstore( + key2, + key1, + { member: "Catania" }, + { radius: feetRadius, unit: GeoUnit.FEET }, + { sortOrder: SortOrder.ASC, storeDist: true }, + ), + ).toEqual(2); + expect( + await client.zrange(key2, { start: 0, stop: -1 }), + ).toEqual(searchResult); + + // Test search by radius, unit: meters, from member + const metersRadius = 200 * 1000; + searchResult = await client.geosearch( + key1, + { member: "Catania" }, + { radius: metersRadius, unit: GeoUnit.METERS }, + { sortOrder: SortOrder.DESC }, + ); + expect(searchResult).toEqual(["Palermo", "Catania"]); + // same with geosearchstore + expect( + await client.geosearchstore( + key2, + key1, + { member: "Catania" }, + { radius: metersRadius, unit: GeoUnit.METERS }, + { sortOrder: SortOrder.DESC, storeDist: true }, + ), + ).toEqual(2); + expect( + await client.zrange(key2, { start: 0, stop: -1 }, true), + ).toEqual(searchResult); + + searchResult = await client.geosearch( + key1, + { member: "Catania" }, + { radius: metersRadius, unit: GeoUnit.METERS }, + { + sortOrder: SortOrder.DESC, + withHash: true, + }, + ); + expect(searchResult).toEqual([ + ["Palermo", [3479099956230698]], + ["Catania", [3479447370796909]], + ]); + + // Test search by radius, unit: miles, from geospatial data + searchResult = await client.geosearch( + key1, + { position: { longitude: 15, latitude: 37 } }, + { radius: 175, unit: GeoUnit.MILES }, + { sortOrder: SortOrder.DESC }, + ); + expect(searchResult).toEqual([ + "edge1", + "edge2", + "Palermo", + "Catania", + ]); + // same with geosearchstore + expect( + await client.geosearchstore( + key2, + key1, + { position: { longitude: 15, latitude: 37 } }, + { radius: 175, unit: GeoUnit.MILES }, + { sortOrder: SortOrder.DESC, storeDist: true }, + ), + ).toEqual(4); + expect( + await client.zrange(key2, { start: 0, stop: -1 }, true), + ).toEqual(searchResult); + + // Test search by radius, unit: kilometers, from a geospatial data, with limited count to 2 + searchResult = await client.geosearch( + key1, + { position: { longitude: 15, latitude: 37 } }, + { radius: 200, unit: GeoUnit.KILOMETERS }, + { + sortOrder: SortOrder.ASC, + count: 2, + withHash: true, + withCoord: true, + withDist: true, + }, + ); + expect(searchResult).toEqual(expectedResult.slice(0, 2)); + // same with geosearchstore + expect( + await client.geosearchstore( + key2, + key1, + { position: { longitude: 15, latitude: 37 } }, + { radius: 200, unit: GeoUnit.KILOMETERS }, + { + sortOrder: SortOrder.ASC, + count: 2, + storeDist: true, + }, + ), + ).toEqual(2); + expect( + await client.zrange(key2, { start: 0, stop: -1 }), + ).toEqual(members.slice(0, 2)); + + // Test search by radius, unit: kilometers, from a geospatial data, with limited ANY count to 1 + searchResult = await client.geosearch( + key1, + { position: { longitude: 15, latitude: 37 } }, + { radius: 200, unit: GeoUnit.KILOMETERS }, + { + sortOrder: SortOrder.ASC, + count: 1, + isAny: true, + withCoord: true, + withDist: true, + withHash: true, + }, + ); + expect(members).toContainEqual(searchResult[0][0]); + // same with geosearchstore + expect( + await client.geosearchstore( + key2, + key1, + { position: { longitude: 15, latitude: 37 } }, + { radius: 200, unit: GeoUnit.KILOMETERS }, + { + sortOrder: SortOrder.ASC, + count: 1, + isAny: true, + }, + ), + ).toEqual(1); + expect( + await client.zrange(key2, { start: 0, stop: -1 }), + ).toEqual([searchResult[0][0]]); + + // no members within the area + searchResult = await client.geosearch( + key1, + { position: { longitude: 15, latitude: 37 } }, + { width: 50, height: 50, unit: GeoUnit.METERS }, + { sortOrder: SortOrder.ASC }, + ); + expect(searchResult).toEqual([]); + // same with geosearchstore + expect( + await client.geosearchstore( + key2, + key1, + { position: { longitude: 15, latitude: 37 } }, + { width: 50, height: 50, unit: GeoUnit.METERS }, + { sortOrder: SortOrder.ASC }, + ), + ).toEqual(0); + expect(await client.zcard(key2)).toEqual(0); + + // no members within the area + searchResult = await client.geosearch( + key1, + { position: { longitude: 15, latitude: 37 } }, + { radius: 5, unit: GeoUnit.METERS }, + { sortOrder: SortOrder.ASC }, + ); + expect(searchResult).toEqual([]); + // same with geosearchstore + expect( + await client.geosearchstore( + key2, + key1, + { position: { longitude: 15, latitude: 37 } }, + { radius: 5, unit: GeoUnit.METERS }, + { sortOrder: SortOrder.ASC }, + ), + ).toEqual(0); + expect(await client.zcard(key2)).toEqual(0); + + // member does not exist + await expect( + client.geosearch( + key1, + { member: "non-existing-member" }, + { radius: 100, unit: GeoUnit.METERS }, + ), + ).rejects.toThrow(RequestError); + await expect( + client.geosearchstore( + key2, + key1, + { member: "non-existing-member" }, + { radius: 100, unit: GeoUnit.METERS }, + ), + ).rejects.toThrow(RequestError); + + // key exists but holds a non-ZSET value + expect(await client.set(key3, uuidv4())).toEqual("OK"); + await expect( + client.geosearch( + key3, + { position: { longitude: 15, latitude: 37 } }, + { radius: 100, unit: GeoUnit.METERS }, + ), + ).rejects.toThrow(RequestError); + await expect( + client.geosearchstore( + key2, + key3, + { position: { longitude: 15, latitude: 37 } }, + { radius: 100, unit: GeoUnit.METERS }, + ), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zmpop test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const nonExistingKey = "{key}-0" + uuidv4(); + const stringKey = "{key}-string" + uuidv4(); + + expect(await client.zadd(key1, { a1: 1, b1: 2 })).toEqual(2); + expect(await client.zadd(key2, { a2: 0.1, b2: 0.2 })).toEqual( + 2, + ); + + expect( + await client.zmpop([key1, key2], ScoreFilter.MAX), + ).toEqual([key1, { b1: 2 }]); + expect( + await client.zmpop([key2, key1], ScoreFilter.MAX, 10), + ).toEqual([key2, { a2: 0.1, b2: 0.2 }]); + + expect( + await client.zmpop([nonExistingKey], ScoreFilter.MIN), + ).toBeNull(); + expect( + await client.zmpop([nonExistingKey], ScoreFilter.MIN, 1), + ).toBeNull(); + + // key exists, but it is not a sorted set + expect(await client.set(stringKey, "value")).toEqual("OK"); + await expect( + client.zmpop([stringKey], ScoreFilter.MAX), + ).rejects.toThrow(RequestError); + await expect( + client.zmpop([stringKey], ScoreFilter.MAX, 1), + ).rejects.toThrow(RequestError); + + // incorrect argument: key list should not be empty + await expect( + client.zmpop([], ScoreFilter.MAX, 1), + ).rejects.toThrow(RequestError); + + // incorrect argument: count should be greater than 0 + await expect( + client.zmpop([key1], ScoreFilter.MAX, 0), + ).rejects.toThrow(RequestError); + + // check that order of entries in the response is preserved + const entries: Record = {}; + + for (let i = 0; i < 10; i++) { + // a0 => 0, a1 => 1 etc + entries["a" + i] = i; + } + + expect(await client.zadd(key2, entries)).toEqual(10); + const result = await client.zmpop([key2], ScoreFilter.MIN, 10); + + if (result) { + expect(result[1]).toEqual(entries); + } + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zincrby test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = "{key}" + uuidv4(); + const member = "{member}-1" + uuidv4(); + const othermember = "{member}-1" + uuidv4(); + const stringKey = "{key}-string" + uuidv4(); + + // key does not exist + expect(await client.zincrby(key, 2.5, member)).toEqual(2.5); + expect(await client.zscore(key, member)).toEqual(2.5); + + // key exists, but value doesn't + expect( + await client.zincrby(Buffer.from(key), -3.3, othermember), + ).toEqual(-3.3); + expect(await client.zscore(key, othermember)).toEqual(-3.3); + + // updating existing value in existing key + expect( + await client.zincrby(key, 1.0, Buffer.from(member)), + ).toEqual(3.5); + expect(await client.zscore(key, member)).toEqual(3.5); + + // Key exists, but it is not a sorted set + expect(await client.set(stringKey, "value")).toEqual("OK"); + await expect( + client.zincrby(stringKey, 0.5, "_"), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zscan test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const initialCursor = "0"; + const defaultCount = 20; + const resultCursorIndex = 0; + const resultCollectionIndex = 1; + + // Setup test data - use a large number of entries to force an iterative cursor. + const numberMap: Record = {}; + + for (let i = 0; i < 50000; i++) { + numberMap[i.toString()] = i; + } + + const charMembers = ["a", "b", "c", "d", "e"]; + const charMap: Record = {}; + const expectedCharMapArray: string[] = []; + + for (let i = 0; i < charMembers.length; i++) { + expectedCharMapArray.push(charMembers[i]); + expectedCharMapArray.push(i.toString()); + charMap[charMembers[i]] = i; + } + + // Empty set + let result = await client.zscan(key1, initialCursor); + expect(result[resultCursorIndex]).toEqual(initialCursor); + expect(result[resultCollectionIndex]).toEqual([]); + + // Negative cursor + if (cluster.checkIfServerVersionLessThan("7.9.0")) { + result = await client.zscan(key1, "-1"); + expect(result[resultCursorIndex]).toEqual(initialCursor); + expect(result[resultCollectionIndex]).toEqual([]); + } else { + try { + expect(await client.zscan(key1, "-1")).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "ResponseError: invalid cursor", + ); + } + } + + // Result contains the whole set + expect(await client.zadd(key1, charMap)).toEqual( + charMembers.length, + ); + result = await client.zscan(key1, initialCursor); + expect(result[resultCursorIndex]).toEqual(initialCursor); + expect(result[resultCollectionIndex].length).toEqual( + expectedCharMapArray.length, + ); + expect(result[resultCollectionIndex]).toEqual( + expectedCharMapArray, + ); + + result = await client.zscan(key1, initialCursor, { + match: "a", + }); + expect(result[resultCursorIndex]).toEqual(initialCursor); + expect(result[resultCollectionIndex]).toEqual(["a", "0"]); + + // Result contains a subset of the key + expect(await client.zadd(key1, numberMap)).toEqual( + Object.keys(numberMap).length, + ); + + result = await client.zscan(key1, initialCursor); + let resultCursor = result[resultCursorIndex]; + let resultIterationCollection = result[resultCollectionIndex]; + let fullResultMapArray: string[] = resultIterationCollection; + let nextResult; + let nextResultCursor; + + // 0 is returned for the cursor of the last iteration. + while (resultCursor != "0") { + nextResult = await client.zscan(key1, resultCursor); + nextResultCursor = nextResult[resultCursorIndex]; + expect(nextResultCursor).not.toEqual(resultCursor); + + expect(nextResult[resultCollectionIndex]).not.toEqual( + resultIterationCollection, + ); + fullResultMapArray = fullResultMapArray.concat( + nextResult[resultCollectionIndex], + ); + resultIterationCollection = + nextResult[resultCollectionIndex]; + resultCursor = nextResultCursor; + } + + // Fetching by cursor is randomized. + const expectedFullMap: Record = { + ...numberMap, + ...charMap, + }; + + expect(fullResultMapArray.length).toEqual( + Object.keys(expectedFullMap).length * 2, + ); + + for (let i = 0; i < fullResultMapArray.length; i += 2) { + expect(fullResultMapArray[i] in expectedFullMap).toEqual( + true, + ); + } + + // Test match pattern + result = await client.zscan(key1, initialCursor, { + match: "*", + }); + expect(result[resultCursorIndex]).not.toEqual(initialCursor); + expect( + result[resultCollectionIndex].length, + ).toBeGreaterThanOrEqual(defaultCount); + + // Test count + result = await client.zscan(key1, initialCursor, { count: 20 }); + expect(result[resultCursorIndex]).not.toEqual("0"); + expect( + result[resultCollectionIndex].length, + ).toBeGreaterThanOrEqual(20); + + // Test count with match returns a non-empty list + result = await client.zscan(key1, initialCursor, { + match: "1*", + count: 20, + }); + expect(result[resultCursorIndex]).not.toEqual("0"); + expect(result[resultCollectionIndex].length).toBeGreaterThan(0); + + // Exceptions + // Non-set key + expect(await client.set(key2, "test")).toEqual("OK"); + await expect(client.zscan(key2, initialCursor)).rejects.toThrow( + RequestError, + ); + await expect( + client.zscan(key2, initialCursor, { + match: "test", + count: 20, + }), + ).rejects.toThrow(RequestError); + + // Negative count + await expect( + client.zscan(key2, initialCursor, { count: -1 }), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `bzmpop test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const nonExistingKey = "{key}-0" + uuidv4(); + const stringKey = "{key}-string" + uuidv4(); + + expect(await client.zadd(key1, { a1: 1, b1: 2 })).toEqual(2); + expect(await client.zadd(key2, { a2: 0.1, b2: 0.2 })).toEqual( + 2, + ); + + expect( + await client.bzmpop([key1, key2], ScoreFilter.MAX, 0.1), + ).toEqual([key1, { b1: 2 }]); + expect( + await client.bzmpop( + [key2, Buffer.from(key1)], + ScoreFilter.MAX, + 0.1, + { + count: 10, + }, + ), + ).toEqual([key2, { a2: 0.1, b2: 0.2 }]); + + // ensure that command doesn't time out even if timeout > request timeout (250ms by default) + expect( + await client.bzmpop([nonExistingKey], ScoreFilter.MAX, 0.5), + ).toBeNull(); + expect( + await client.bzmpop( + [nonExistingKey], + ScoreFilter.MAX, + 0.55, + { count: 1 }, + ), + ).toBeNull(); + + // key exists, but it is not a sorted set + expect(await client.set(stringKey, "value")).toEqual("OK"); + await expect( + client.bzmpop([stringKey], ScoreFilter.MAX, 0.1), + ).rejects.toThrow(RequestError); + await expect( + client.bzmpop([stringKey], ScoreFilter.MAX, 0.1, { + count: 1, + }), + ).rejects.toThrow(RequestError); + + // incorrect argument: key list should not be empty + await expect( + client.bzmpop([], ScoreFilter.MAX, 0.1, { count: 1 }), + ).rejects.toThrow(RequestError); + + // incorrect argument: count should be greater than 0 + await expect( + client.bzmpop([key1], ScoreFilter.MAX, 0.1, { count: 0 }), + ).rejects.toThrow(RequestError); + + // incorrect argument: timeout can not be a negative number + await expect( + client.bzmpop([key1], ScoreFilter.MAX, -1, { count: 10 }), + ).rejects.toThrow(RequestError); + + // check that order of entries in the response is preserved + const entries: Record = {}; + + for (let i = 0; i < 10; i++) { + // a0 => 0, a1 => 1 etc + entries["a" + i] = i; + } + + expect(await client.zadd(key2, entries)).toEqual(10); + const result = await client.bzmpop( + [key2], + ScoreFilter.MIN, + 0.1, + { count: 10 }, + ); + + if (result) { + expect(result[1]).toEqual(entries); + } + + // TODO: add test case with 0 timeout (no timeout) should never time out, + // but we wrap the test with timeout to avoid test failing or stuck forever + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `geodist test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + const member1 = "Palermo"; + const member2 = "Catania"; + const nonExistingMember = "NonExisting"; + const expected = 166274.1516; + const expectedKM = 166.2742; + const delta = 1e-9; + + // adding the geo locations + const membersToCoordinates = new Map(); + membersToCoordinates.set(member1, { + longitude: 13.361389, + latitude: 38.115556, + }); + membersToCoordinates.set(member2, { + longitude: 15.087269, + latitude: 37.502669, + }); + expect(await client.geoadd(key1, membersToCoordinates)).toBe(2); + + // checking result with default metric + expect( + await client.geodist(key1, member1, member2), + ).toBeCloseTo(expected, delta); + + // checking result with metric specification of kilometers + expect( + await client.geodist( + key1, + member1, + member2, + GeoUnit.KILOMETERS, + ), + ).toBeCloseTo(expectedKM, delta); + + // null result when member index is missing + expect( + await client.geodist(key1, member1, nonExistingMember), + ).toBeNull(); + + // key exists but holds non-ZSET value + expect(await client.set(key2, "geodist")).toBe("OK"); + await expect( + client.geodist(key2, member1, member2), + ).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `geohash test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + const members = ["Palermo", "Catania", "NonExisting"]; + const empty: string[] = []; + const expected = ["sqc8b49rny0", "sqdtr74hyu0", null]; + + // adding the geo locations + const membersToCoordinates = new Map(); + membersToCoordinates.set("Palermo", { + longitude: 13.361389, + latitude: 38.115556, + }); + membersToCoordinates.set("Catania", { + longitude: 15.087269, + latitude: 37.502669, + }); + expect(await client.geoadd(key1, membersToCoordinates)).toBe(2); + + // checking result with default metric + expect(await client.geohash(key1, members)).toEqual(expected); + + // empty members array + expect(await (await client.geohash(key1, empty)).length).toBe( + 0, + ); + + // key exists but holds non-ZSET value + expect(await client.set(key2, "geohash")).toBe("OK"); + await expect(client.geohash(key2, members)).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `geo commands binary %p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = "{geo-bin}-1-" + uuidv4(); + const key2 = "{geo-bin}-2-" + uuidv4(); + + const members = [ + "Catania", + Buffer.from("Palermo"), + "edge2", + "edge1", + ]; + const membersCoordinates: [number, number][] = [ + [15.087269, 37.502669], + [13.361389, 38.115556], + [17.24151, 38.788135], + [12.758489, 38.788135], + ]; + + const membersGeoData: GeospatialData[] = []; + + for (const [lon, lat] of membersCoordinates) { + membersGeoData.push({ longitude: lon, latitude: lat }); + } + + const membersToCoordinates = new Map(); + + for (let i = 0; i < members.length; i++) { + membersToCoordinates.set(members[i], membersGeoData[i]); + } + + // geoadd + expect( + await client.geoadd( + Buffer.from(key1), + membersToCoordinates, + ), + ).toBe(4); + // geopos + const geopos = await client.geopos(Buffer.from(key1), [ + "Palermo", + Buffer.from("Catania"), + "New York", + ]); + // inner array is possibly null, we need a null check or a cast + expect(geopos[0]?.[0]).toBeCloseTo(13.361389, 5); + expect(geopos[0]?.[1]).toBeCloseTo(38.115556, 5); + expect(geopos[1]?.[0]).toBeCloseTo(15.087269, 5); + expect(geopos[1]?.[1]).toBeCloseTo(37.502669, 5); + expect(geopos[2]).toBeNull(); + // geohash + const geohash = await client.geohash(Buffer.from(key1), [ + "Palermo", + Buffer.from("Catania"), + "New York", + ]); + expect(geohash).toEqual(["sqc8b49rny0", "sqdtr74hyu0", null]); + // geodist + expect( + await client.geodist( + Buffer.from(key1), + Buffer.from("Palermo"), + "Catania", + ), + ).toBeCloseTo(166274.1516, 5); + + // geosearch with binary decoder + let searchResult = await client.geosearch( + Buffer.from(key1), + { position: { longitude: 15, latitude: 37 } }, + { width: 400, height: 400, unit: GeoUnit.KILOMETERS }, + { decoder: Decoder.Bytes }, + ); + // using set to compare, because results are reordrered + expect(new Set(searchResult)).toEqual( + new Set(members.map((m) => Buffer.from(m))), + ); + // repeat geosearch with string decoder + searchResult = await client.geosearch( + Buffer.from(key1), + { position: { longitude: 15, latitude: 37 } }, + { width: 400, height: 400, unit: GeoUnit.KILOMETERS }, + ); + // using set to compare, because results are reordrered + expect(new Set(searchResult)).toEqual( + new Set(members.map((m) => m.toString())), + ); + // same with geosearchstore + expect( + await client.geosearchstore( + Buffer.from(key2), + Buffer.from(key1), + { position: { longitude: 15, latitude: 37 } }, + { width: 400, height: 400, unit: GeoUnit.KILOMETERS }, + ), + ).toEqual(4); + expect( + await client.zrange(key2, { start: 0, stop: -1 }), + ).toEqual(searchResult); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `touch test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = `{key}-${uuidv4()}`; + const key2 = `{key}-${uuidv4()}`; + const nonExistingKey = `{key}-${uuidv4()}`; + + expect( + await client.mset({ [key1]: "value1", [key2]: "value2" }), + ).toEqual("OK"); + expect(await client.touch([key1, Buffer.from(key2)])).toEqual( + 2, + ); + expect( + await client.touch([key2, nonExistingKey, key1]), + ).toEqual(2); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrandmember test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + + const memberScores = { one: 1.0, two: 2.0 }; + const elements = ["one", "two"]; + expect(await client.zadd(key1, memberScores)).toBe(2); + + // check random memember belongs to the set + const randmember = await client.zrandmember(key1); + + if (randmember !== null) { + expect(elements.includes(randmember)).toEqual(true); + } + + // non existing key should return null + expect(await client.zrandmember("nonExistingKey")).toBeNull(); + + // Key exists, but is not a set + expect(await client.set(key2, "foo")).toBe("OK"); + await expect(client.zrandmember(key2)).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrandmemberWithCount test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + + const memberScores = { one: 1.0, two: 2.0 }; + expect(await client.zadd(key1, memberScores)).toBe(2); + + // unique values are expected as count is positive + let randMembers = await client.zrandmemberWithCount(key1, 4); + expect(randMembers.length).toBe(2); + expect(randMembers.length).toEqual(new Set(randMembers).size); + + // Duplicate values are expected as count is negative + randMembers = await client.zrandmemberWithCount(key1, -4); + expect(randMembers.length).toBe(4); + const randMemberSet = new Set(); + + for (const member of randMembers) { + const memberStr = member + ""; + + if (!randMemberSet.has(memberStr)) { + randMemberSet.add(memberStr); + } + } + + expect(randMembers.length).not.toEqual(randMemberSet.size); + + // non existing key should return empty array + randMembers = await client.zrandmemberWithCount( + "nonExistingKey", + -4, + ); + expect(randMembers.length).toBe(0); + + // Key exists, but is not a set + expect(await client.set(key2, "foo")).toBe("OK"); + await expect( + client.zrandmemberWithCount(key2, 1), + ).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrandmemberWithCountWithScores test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + + const memberScores = { one: 1.0, two: 2.0 }; + const memberScoreMap = new Map([ + ["one", 1.0], + ["two", 2.0], + ]); + expect(await client.zadd(key1, memberScores)).toBe(2); + + // unique values are expected as count is positive + let randMembers = await client.zrandmemberWithCountWithScores( + key1, + 4, + ); + + for (const member of randMembers) { + const key = String(member[0]); + const score = Number(member[1]); + expect(score).toEqual(memberScoreMap.get(key)); + } + + // Duplicate values are expected as count is negative + randMembers = await client.zrandmemberWithCountWithScores( + key1, + -4, + ); + expect(randMembers.length).toBe(4); + const keys = []; + + for (const member of randMembers) { + keys.push(String(member[0])); + } + + expect(randMembers.length).not.toEqual(new Set(keys).size); + + // non existing key should return empty array + randMembers = await client.zrandmemberWithCountWithScores( + "nonExistingKey", + -4, + ); + expect(randMembers.length).toBe(0); + + // Key exists, but is not a set + expect(await client.set(key2, "foo")).toBe("OK"); + await expect( + client.zrandmemberWithCount(key2, 1), + ).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `lcs %p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + + const key1 = "{lcs}" + uuidv4(); + const key2 = "{lcs}" + uuidv4(); + const key3 = "{lcs}" + uuidv4(); + const key4 = "{lcs}" + uuidv4(); + + // keys does not exist or is empty + expect(await client.lcs(key1, key2)).toEqual(""); + expect( + await client.lcs(Buffer.from(key1), Buffer.from(key2)), + ).toEqual(""); + expect(await client.lcsLen(key1, key2)).toEqual(0); + expect( + await client.lcsLen(Buffer.from(key1), Buffer.from(key2)), + ).toEqual(0); + expect(await client.lcsIdx(key1, key2)).toEqual({ + matches: [], + len: 0, + }); + expect( + await client.lcsIdx(Buffer.from(key1), Buffer.from(key2)), + ).toEqual({ + matches: [], + len: 0, + }); + + // LCS with some strings + expect( + await client.mset({ + [key1]: "abcdefghijk", + [key2]: "defjkjuighijk", + [key3]: "123", + }), + ).toEqual("OK"); + expect(await client.lcs(key1, key2)).toEqual("defghijk"); + expect(await client.lcsLen(key1, key2)).toEqual(8); + + // LCS with only IDX + expect(await client.lcsIdx(key1, key2)).toEqual({ + matches: [ + [ + [6, 10], + [8, 12], + ], + [ + [3, 5], + [0, 2], + ], + ], + len: 8, + }); + expect(await client.lcsIdx(key1, key2, {})).toEqual({ + matches: [ + [ + [6, 10], + [8, 12], + ], + [ + [3, 5], + [0, 2], + ], + ], + len: 8, + }); + expect( + await client.lcsIdx(key1, key2, { withMatchLen: false }), + ).toEqual({ + matches: [ + [ + [6, 10], + [8, 12], + ], + [ + [3, 5], + [0, 2], + ], + ], + len: 8, + }); + + // LCS with IDX and WITHMATCHLEN + expect( + await client.lcsIdx(key1, key2, { withMatchLen: true }), + ).toEqual({ + matches: [ + [[6, 10], [8, 12], 5], + [[3, 5], [0, 2], 3], + ], + len: 8, + }); + + // LCS with IDX and MINMATCHLEN + expect( + await client.lcsIdx(key1, key2, { minMatchLen: 4 }), + ).toEqual({ + matches: [ + [ + [6, 10], + [8, 12], + ], + ], + len: 8, + }); + // LCS with IDX and a negative MINMATCHLEN + expect( + await client.lcsIdx(key1, key2, { minMatchLen: -1 }), + ).toEqual({ + matches: [ + [ + [6, 10], + [8, 12], + ], + [ + [3, 5], + [0, 2], + ], + ], + len: 8, + }); + + // LCS with IDX, MINMATCHLEN, and WITHMATCHLEN + expect( + await client.lcsIdx(key1, key2, { + minMatchLen: 4, + withMatchLen: true, + }), + ).toEqual({ matches: [[[6, 10], [8, 12], 5]], len: 8 }); + + // non-string keys are used + expect(await client.sadd(key4, ["_"])).toEqual(1); + await expect(client.lcs(key1, key4)).rejects.toThrow( + RequestError, + ); + await expect(client.lcsLen(key1, key4)).rejects.toThrow( + RequestError, + ); + await expect(client.lcsIdx(key1, key4)).rejects.toThrow( + RequestError, + ); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `xdel test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const stringKey = uuidv4(); + const nonExistentKey = uuidv4(); + const streamId1 = "0-1"; + const streamId2 = "0-2"; + const streamId3 = "0-3"; + + expect( + await client.xadd( + key, + [ + ["f1", "foo1"], + ["f2", "foo2"], + ], + { id: streamId1 }, + ), + ).toEqual(streamId1); + + expect( + await client.xadd( + key, + [ + ["f1", "foo1"], + ["f2", "foo2"], + ], + { id: streamId2 }, + ), + ).toEqual(streamId2); + + expect(await client.xlen(key)).toEqual(2); + + // deletes one stream id, and ignores anything invalid + expect(await client.xdel(key, [streamId1, streamId3])).toEqual( + 1, + ); + expect( + await client.xdel(Buffer.from(nonExistentKey), [ + Buffer.from(streamId3), + ]), + ).toEqual(0); + + // invalid argument - id list should not be empty + await expect(client.xdel(key, [])).rejects.toThrow( + RequestError, + ); + + // key exists, but it is not a stream + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.xdel(stringKey, [streamId3]), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `xinfoconsumers xinfo consumers %p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + const key = uuidv4(); + const stringKey = uuidv4(); + const groupName1 = uuidv4(); + const consumer1 = uuidv4(); + const consumer2 = uuidv4(); + const streamId1 = "0-1"; + const streamId2 = "0-2"; + const streamId3 = "0-3"; + const streamId4 = "0-4"; + + expect( + await client.xadd( + key, + [ + ["entry1_field1", "entry1_value1"], + ["entry1_field2", "entry1_value2"], + ], + { id: streamId1 }, + ), + ).toEqual(streamId1); + + expect( + await client.xadd( + key, + [ + ["entry2_field1", "entry2_value1"], + ["entry2_field2", "entry2_value2"], + ], + { id: streamId2 }, + ), + ).toEqual(streamId2); + + expect( + await client.xadd( + key, + [["entry3_field1", "entry3_value1"]], + { id: streamId3 }, + ), + ).toEqual(streamId3); + + expect( + await client.xgroupCreate(key, groupName1, "0-0"), + ).toEqual("OK"); + + expect( + await client.xreadgroup( + groupName1, + consumer1, + { [key]: ">" }, + { count: 1 }, + ), + ).toEqual({ + [key]: { + [streamId1]: [ + ["entry1_field1", "entry1_value1"], + ["entry1_field2", "entry1_value2"], + ], + }, + }); + // Sleep to ensure the idle time value and inactive time value returned by xinfo_consumers is > 0 + await new Promise((resolve) => setTimeout(resolve, 2000)); + let result = await client.xinfoConsumers(key, groupName1); + expect(result.length).toEqual(1); + expect(result[0].name).toEqual(consumer1); + expect(result[0].pending).toEqual(1); + expect(result[0].idle).toBeGreaterThan(0); + + if (cluster.checkIfServerVersionLessThan("7.2.0")) { + expect(result[0].inactive).toBeGreaterThan(0); + } + + expect( + await client.xgroupCreateConsumer( + key, + groupName1, + consumer2, + ), + ).toBeTruthy(); + expect( + await client.xreadgroup(groupName1, consumer2, { + [key]: ">", + }), + ).toEqual({ + [key]: { + [streamId2]: [ + ["entry2_field1", "entry2_value1"], + ["entry2_field2", "entry2_value2"], + ], + [streamId3]: [["entry3_field1", "entry3_value1"]], + }, + }); + + // Verify that xinfo_consumers contains info for 2 consumers now + result = await client.xinfoConsumers(key, groupName1); + expect(result.length).toEqual(2); + + // key exists, but it is not a stream + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.xinfoConsumers(stringKey, "_"), + ).rejects.toThrow(RequestError); + + // Passing a non-existing key raises an error + const key2 = uuidv4(); + await expect(client.xinfoConsumers(key2, "_")).rejects.toThrow( + RequestError, + ); + + expect( + await client.xadd(key2, [["field", "value"]], { + id: streamId4, + }), + ).toEqual(streamId4); + + // Passing a non-existing group raises an error + await expect(client.xinfoConsumers(key2, "_")).rejects.toThrow( + RequestError, + ); + + expect( + await client.xgroupCreate(key2, groupName1, "0-0"), + ).toEqual("OK"); + expect(await client.xinfoConsumers(key2, groupName1)).toEqual( + [], + ); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `xinfogroups xinfo groups %p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + const key = uuidv4(); + const stringKey = uuidv4(); + const groupName1 = uuidv4(); + const consumer1 = uuidv4(); + const streamId1 = "0-1"; + const streamId2 = "0-2"; + const streamId3 = "0-3"; + + expect( + await client.xgroupCreate(key, groupName1, "0-0", { + mkStream: true, + }), + ).toEqual("OK"); + + // one empty group exists + expect(await client.xinfoGroups(key)).toEqual( + cluster.checkIfServerVersionLessThan("7.0.0") + ? [ + { + name: groupName1, + consumers: 0, + pending: 0, + "last-delivered-id": "0-0", + }, + ] + : [ + { + name: groupName1, + consumers: 0, + pending: 0, + "last-delivered-id": "0-0", + "entries-read": null, + lag: 0, + }, + ], + ); + + expect( + await client.xadd( + key, + [ + ["entry1_field1", "entry1_value1"], + ["entry1_field2", "entry1_value2"], + ], + { id: streamId1 }, + ), + ).toEqual(streamId1); + + expect( + await client.xadd( + key, + [ + ["entry2_field1", "entry2_value1"], + ["entry2_field2", "entry2_value2"], + ], + { id: streamId2 }, + ), + ).toEqual(streamId2); + + expect( + await client.xadd( + key, + [["entry3_field1", "entry3_value1"]], + { id: streamId3 }, + ), + ).toEqual(streamId3); + + // same as previous check, bug lag = 3, there are 3 messages unread + expect(await client.xinfoGroups(key)).toEqual( + cluster.checkIfServerVersionLessThan("7.0.0") + ? [ + { + name: groupName1, + consumers: 0, + pending: 0, + "last-delivered-id": "0-0", + }, + ] + : [ + { + name: groupName1, + consumers: 0, + pending: 0, + "last-delivered-id": "0-0", + "entries-read": null, + lag: 3, + }, + ], + ); + + expect( + await client.customCommand([ + "XREADGROUP", + "GROUP", + groupName1, + consumer1, + "STREAMS", + key, + ">", + ]), + ).toEqual({ + [key]: { + [streamId1]: [ + ["entry1_field1", "entry1_value1"], + ["entry1_field2", "entry1_value2"], + ], + [streamId2]: [ + ["entry2_field1", "entry2_value1"], + ["entry2_field2", "entry2_value2"], + ], + [streamId3]: [["entry3_field1", "entry3_value1"]], + }, + }); + // after reading, `lag` is reset, and `pending`, consumer count and last ID are set + expect(await client.xinfoGroups(key)).toEqual( + cluster.checkIfServerVersionLessThan("7.0.0") + ? [ + { + name: groupName1, + consumers: 1, + pending: 3, + "last-delivered-id": streamId3, + }, + ] + : [ + { + name: groupName1, + consumers: 1, + pending: 3, + "last-delivered-id": streamId3, + "entries-read": 3, + lag: 0, + }, + ], + ); + + expect( + await client.customCommand([ + "XACK", + key, + groupName1, + streamId1, + ]), + ).toEqual(1); + // once message ack'ed, pending counter decreased + expect(await client.xinfoGroups(key)).toEqual( + cluster.checkIfServerVersionLessThan("7.0.0") + ? [ + { + name: groupName1, + consumers: 1, + pending: 2, + "last-delivered-id": streamId3, + }, + ] + : [ + { + name: groupName1, + consumers: 1, + pending: 2, + "last-delivered-id": streamId3, + "entries-read": 3, + lag: 0, + }, + ], + ); + + // key exists, but it is not a stream + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect(client.xinfoGroups(stringKey)).rejects.toThrow( + RequestError, + ); + + // Passing a non-existing key raises an error + const key2 = uuidv4(); + await expect(client.xinfoGroups(key2)).rejects.toThrow( + RequestError, + ); + // create a second stream + await client.xadd(key2, [["a", "b"]]); + // no group yet exists + expect(await client.xinfoGroups(key2)).toEqual([]); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `xgroupSetId test %p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + const key = "testKey" + uuidv4(); + const nonExistingKey = "group" + uuidv4(); + const stringKey = "testKey" + uuidv4(); + const groupName = uuidv4(); + const consumerName = uuidv4(); + const streamid0 = "0"; + const streamid1_0 = "1-0"; + const streamid1_1 = "1-1"; + const streamid1_2 = "1-2"; + + // Setup: Create stream with 3 entries, create consumer group, read entries to add them to the Pending Entries List + expect( + await client.xadd(key, [["f0", "v0"]], { id: streamid1_0 }), + ).toBe(streamid1_0); + expect( + await client.xadd(key, [["f1", "v1"]], { id: streamid1_1 }), + ).toBe(streamid1_1); + expect( + await client.xadd(key, [["f2", "v2"]], { id: streamid1_2 }), + ).toBe(streamid1_2); + + expect( + await client.xgroupCreate(key, groupName, streamid0), + ).toBe("OK"); + + expect( + await client.xreadgroup(groupName, consumerName, { + [key]: ">", + }), + ).toEqual({ + [key]: { + [streamid1_0]: [["f0", "v0"]], + [streamid1_1]: [["f1", "v1"]], + [streamid1_2]: [["f2", "v2"]], + }, + }); + + // Sanity check: xreadgroup should not return more entries since they're all already in the + // Pending Entries List. + expect( + await client.xreadgroup(groupName, consumerName, { + [key]: ">", + }), + ).toBeNull(); + + // Reset the last delivered ID for the consumer group to "1-1" + if (cluster.checkIfServerVersionLessThan("7.0.0")) { + expect( + await client.xgroupSetId(key, groupName, streamid1_1), + ).toBe("OK"); + } else { + expect( + await client.xgroupSetId( + key, + groupName, + streamid1_1, + 1, + ), + ).toBe("OK"); + } + + // xreadgroup should only return entry 1-2 since we reset the last delivered ID to 1-1 + const newResult = await client.xreadgroup( + groupName, + consumerName, + { [key]: ">" }, + ); + expect(newResult).toEqual({ + [key]: { + [streamid1_2]: [["f2", "v2"]], + }, + }); + + // An error is raised if XGROUP SETID is called with a non-existing key + await expect( + client.xgroupSetId(nonExistingKey, groupName, streamid0), + ).rejects.toThrow(RequestError); + + // An error is raised if XGROUP SETID is called with a non-existing group + await expect( + client.xgroupSetId(key, "non_existing_group", streamid0), + ).rejects.toThrow(RequestError); + + // Setting the ID to a non-existing ID is allowed + expect(await client.xgroupSetId(key, groupName, "99-99")).toBe( + "OK", + ); + + // Testing binary parameters with an non-existing ID + expect( + await client.xgroupSetId( + Buffer.from(key), + Buffer.from(groupName), + Buffer.from("99-99"), + ), + ).toBe("OK"); + + // key exists, but is not a stream + expect(await client.set(stringKey, "xgroup setid")).toBe("OK"); + await expect( + client.xgroupSetId(stringKey, groupName, streamid1_0), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `xpending test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + const key = uuidv4(); + const group = uuidv4(); + + expect( + await client.xgroupCreate(key, group, "0", { + mkStream: true, + }), + ).toEqual("OK"); + expect( + await client.customCommand([ + "xgroup", + "createconsumer", + key, + group, + "consumer", + ]), + ).toEqual(true); + + expect( + await client.xadd( + key, + [ + ["entry1_field1", "entry1_value1"], + ["entry1_field2", "entry1_value2"], + ], + { id: "0-1" }, + ), + ).toEqual("0-1"); + expect( + await client.xadd( + key, + [["entry2_field1", "entry2_value1"]], + { id: "0-2" }, + ), + ).toEqual("0-2"); + + expect( + await client.xreadgroup(group, "consumer", { [key]: ">" }), + ).toEqual({ + [key]: { + "0-1": [ + ["entry1_field1", "entry1_value1"], + ["entry1_field2", "entry1_value2"], + ], + "0-2": [["entry2_field1", "entry2_value1"]], + }, + }); + + // wait to get some minIdleTime + await new Promise((resolve) => setTimeout(resolve, 500)); + + expect(await client.xpending(key, group)).toEqual([ + 2, + "0-1", + "0-2", + [["consumer", "2"]], + ]); + + const result = await client.xpendingWithOptions( + key, + group, + cluster.checkIfServerVersionLessThan("6.2.0") + ? { + start: InfBoundary.NegativeInfinity, + end: InfBoundary.PositiveInfinity, + count: 1, + } + : { + start: InfBoundary.NegativeInfinity, + end: InfBoundary.PositiveInfinity, + count: 1, + minIdleTime: 42, + }, + ); + result[0][2] = 0; // overwrite msec counter to avoid test flakyness + expect(result).toEqual([["0-1", "consumer", 0, 1]]); + + // not existing consumer + expect( + await client.xpendingWithOptions(key, group, { + start: { value: "0-1", isInclusive: true }, + end: { value: "0-2", isInclusive: false }, + count: 12, + consumer: "_", + }), + ).toEqual([]); + + // key exists, but it is not a stream + const stringKey = uuidv4(); + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect(client.xpending(stringKey, "_")).rejects.toThrow( + RequestError, + ); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `xclaim test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const group = uuidv4(); + + expect( + await client.xgroupCreate(key, group, "0", { + mkStream: true, + }), + ).toEqual("OK"); + expect( + await client.xgroupCreateConsumer(key, group, "consumer"), + ).toEqual(true); + + expect( + await client.xadd( + key, + [ + ["entry1_field1", "entry1_value1"], + ["entry1_field2", "entry1_value2"], + ], + { id: "0-1" }, + ), + ).toEqual("0-1"); + expect( + await client.xadd( + key, + [["entry2_field1", "entry2_value1"]], + { id: "0-2" }, + ), + ).toEqual("0-2"); + + expect( + await client.xreadgroup(group, "consumer", { [key]: ">" }), + ).toEqual({ + [key]: { + "0-1": [ + ["entry1_field1", "entry1_value1"], + ["entry1_field2", "entry1_value2"], + ], + "0-2": [["entry2_field1", "entry2_value1"]], + }, + }); + + expect( + await client.xclaim(key, group, "consumer", 0, ["0-1"]), + ).toEqual({ + "0-1": [ + ["entry1_field1", "entry1_value1"], + ["entry1_field2", "entry1_value2"], + ], + }); + expect( + await client.xclaimJustId(key, group, "consumer", 0, [ + "0-2", + ]), + ).toEqual(["0-2"]); + + // add one more entry + expect( + await client.xadd( + key, + [["entry3_field1", "entry3_value1"]], + { id: "0-3" }, + ), + ).toEqual("0-3"); + // using force, we can xclaim the message without reading it + expect( + await client.xclaimJustId( + key, + group, + "consumer", + 0, + ["0-3"], + { isForce: true, retryCount: 99 }, + ), + ).toEqual(["0-3"]); + + // incorrect IDs - response is empty + expect( + await client.xclaim( + Buffer.from(key), + Buffer.from(group), + Buffer.from("consumer"), + 0, + [Buffer.from("000")], + ), + ).toEqual({}); + expect( + await client.xclaimJustId(key, group, "consumer", 0, [ + "000", + ]), + ).toEqual([]); + + // empty ID array + await expect( + client.xclaim(key, group, "consumer", 0, []), + ).rejects.toThrow(RequestError); - const setResGetNotExistOld = await client.set(key, value, { - returnOldValue: true, - }); - // key does not exist, so old value should be null - checkSimple(setResGetNotExistOld).toEqual(null); - // key should have been set - const getResGetNotExistOld = await client.get(key); - checkSimple(getResGetNotExistOld).toEqual(value); + // key exists, but it is not a stream + const stringKey = uuidv4(); + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.xclaim(stringKey, "_", "_", 0, ["_"]), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); - const setResGetExistOld = await client.set(key, value, { - returnOldValue: true, - }); - // key exists, so old value should be returned - checkSimple(setResGetExistOld).toEqual(value); - // key should have been set - const getResGetExistOld = await client.get(key); - checkSimple(getResGetExistOld).toEqual(value); - } + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `xautoclaim test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster) => { + const key = uuidv4(); + const group = uuidv4(); - async function setWithAllOptions(client: BaseClient) { - const key = uuidv4(); - const value = uuidv4(); + expect( + await client.xgroupCreate(key, group, "0", { + mkStream: true, + }), + ).toEqual("OK"); + expect( + await client.xgroupCreateConsumer(key, group, "consumer"), + ).toEqual(true); - // set with multiple options: - // * only apply SET if the key already exists - // * expires after 1 second - // * returns the old value - const setResWithAllOptions = await client.set(key, value, { - expiry: { - type: "unixSeconds", - count: Math.floor(Date.now() / 1000) + 1, - }, - conditionalSet: "onlyIfExists", - returnOldValue: true, - }); - // key does not exist, so old value should be null - expect(setResWithAllOptions).toEqual(null); - // key does not exist, so SET should not have applied - expect(await client.get(key)).toEqual(null); - } + expect( + await client.xadd( + key, + [ + ["entry1_field1", "entry1_value1"], + ["entry1_field2", "entry1_value2"], + ], + { id: "0-1" }, + ), + ).toEqual("0-1"); + expect( + await client.xadd( + key, + [["entry2_field1", "entry2_value1"]], + { id: "0-2" }, + ), + ).toEqual("0-2"); - async function testSetWithAllCombination(client: BaseClient) { - const key = uuidv4(); - const value = uuidv4(); - const count = 2; - const expiryCombination = [ - { type: "seconds", count }, - { type: "unixSeconds", count }, - { type: "unixMilliseconds", count }, - { type: "milliseconds", count }, - "keepExisting", - ]; - let exist = false; + expect( + await client.xreadgroup(group, "consumer", { [key]: ">" }), + ).toEqual({ + [key]: { + "0-1": [ + ["entry1_field1", "entry1_value1"], + ["entry1_field2", "entry1_value2"], + ], + "0-2": [["entry2_field1", "entry2_value1"]], + }, + }); - for (const expiryVal of expiryCombination) { - const setRes = await client.set(key, value, { - expiry: expiryVal as - | "keepExisting" - | { - type: - | "seconds" - | "milliseconds" - | "unixSeconds" - | "unixMilliseconds"; - count: number; - }, - conditionalSet: "onlyIfDoesNotExist", - }); + // testing binary parameters + let result = await client.xautoclaim( + Buffer.from(key), + Buffer.from(group), + Buffer.from("consumer"), + 0, + Buffer.from("0-0"), + 1, + ); + let expected: typeof result = [ + "0-2", + { + "0-1": [ + ["entry1_field1", "entry1_value1"], + ["entry1_field2", "entry1_value2"], + ], + }, + ]; + if (!cluster.checkIfServerVersionLessThan("7.0.0")) + expected.push([]); + expect(result).toEqual(expected); - if (exist == false) { - checkSimple(setRes).toEqual("OK"); - exist = true; - } else { - checkSimple(setRes).toEqual(null); - } + let result2 = await client.xautoclaimJustId( + key, + group, + "consumer", + 0, + "0-0", + ); + let expected2: typeof result2 = ["0-0", ["0-1", "0-2"]]; + if (!cluster.checkIfServerVersionLessThan("7.0.0")) + expected2.push([]); + expect(result2).toEqual(expected2); - const getRes = await client.get(key); - checkSimple(getRes).toEqual(value); - } + // add one more entry + expect( + await client.xadd( + key, + [["entry3_field1", "entry3_value1"]], + { id: "0-3" }, + ), + ).toEqual("0-3"); - for (const expiryVal of expiryCombination) { - const setRes = await client.set(key, value, { - expiry: expiryVal as - | "keepExisting" - | { - type: - | "seconds" - | "milliseconds" - | "unixSeconds" - | "unixMilliseconds"; - count: number; - }, + // incorrect IDs - response is empty + result = await client.xautoclaim( + key, + group, + "consumer", + 0, + "5-0", + ); + expected = ["0-0", {}]; + if (!cluster.checkIfServerVersionLessThan("7.0.0")) + expected.push([]); + expect(result).toEqual(expected); - conditionalSet: "onlyIfExists", - returnOldValue: true, - }); + result2 = await client.xautoclaimJustId( + key, + group, + "consumer", + 0, + "5-0", + ); + expected2 = ["0-0", []]; + if (!cluster.checkIfServerVersionLessThan("7.0.0")) + expected2.push([]); + expect(result2).toEqual(expected2); - expect(setRes).toBeDefined(); - } - } + // key exists, but it is not a stream + const stringKey = uuidv4(); + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.xautoclaim(stringKey, "_", "_", 0, "_"), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "Set commands with options test_%p", + `xack test_%p`, async (protocol) => { await runTest(async (client: BaseClient) => { - await setWithExpiryOptions(client); - await setWithOnlyIfExistOptions(client); - await setWithOnlyIfNotExistOptions(client); - await setWithGetOldOptions(client); - await setWithAllOptions(client); - await testSetWithAllCombination(client); + const key = "{testKey}:1-" + uuidv4(); + const nonExistingKey = "{testKey}:2-" + uuidv4(); + const string_key = "{testKey}:3-" + uuidv4(); + const groupName = uuidv4(); + const consumerName = uuidv4(); + const stream_id0 = "0"; + const stream_id1_0 = "1-0"; + const stream_id1_1 = "1-1"; + const stream_id1_2 = "1-2"; + + // setup: add 2 entries to the stream, create consumer group and read to mark them as pending + expect( + await client.xadd(key, [["f0", "v0"]], { + id: stream_id1_0, + }), + ).toEqual(stream_id1_0); + expect( + await client.xadd(key, [["f1", "v1"]], { + id: stream_id1_1, + }), + ).toEqual(stream_id1_1); + expect( + await client.xgroupCreate(key, groupName, stream_id0), + ).toBe("OK"); + expect( + await client.xreadgroup(groupName, consumerName, { + [key]: ">", + }), + ).toEqual({ + [key]: { + [stream_id1_0]: [["f0", "v0"]], + [stream_id1_1]: [["f1", "v1"]], + }, + }); + + // add one more entry + expect( + await client.xadd(key, [["f2", "v2"]], { + id: stream_id1_2, + }), + ).toEqual(stream_id1_2); + + // acknowledge the first 2 entries + expect( + await client.xack(key, groupName, [ + stream_id1_0, + stream_id1_1, + ]), + ).toBe(2); + + // attempt to acknowledge the first 2 entries again, returns 0 since they were already acknowledged + expect( + await client.xack(key, groupName, [ + stream_id1_0, + stream_id1_1, + ]), + ).toBe(0); + + // testing binary parameters + expect( + await client.xack( + Buffer.from(key), + Buffer.from(groupName), + [Buffer.from(stream_id1_0), Buffer.from(stream_id1_1)], + ), + ).toBe(0); + + // read the last unacknowledged entry + expect( + await client.xreadgroup(groupName, consumerName, { + [key]: ">", + }), + ).toEqual({ [key]: { [stream_id1_2]: [["f2", "v2"]] } }); + + // deleting the consumer, returns 1 since the last entry still hasn't been acknowledged + expect( + await client.xgroupDelConsumer( + key, + groupName, + consumerName, + ), + ).toBe(1); + + // attempt to acknowledge a non-existing key, returns 0 + expect( + await client.xack(nonExistingKey, groupName, [ + stream_id1_0, + ]), + ).toBe(0); + + // attempt to acknowledge a non-existing group name, returns 0 + expect( + await client.xack(key, "nonExistingGroup", [stream_id1_0]), + ).toBe(0); + + // attempt to acknowledge a non-existing ID, returns 0 + expect(await client.xack(key, groupName, ["99-99"])).toBe(0); + + // invalid argument - ID list must not be empty + await expect(client.xack(key, groupName, [])).rejects.toThrow( + RequestError, + ); + + // invalid argument - invalid stream ID format + await expect( + client.xack(key, groupName, ["invalid stream ID format"]), + ).rejects.toThrow(RequestError); + + // key exists, but is not a stream + expect(await client.set(string_key, "xack")).toBe("OK"); + await expect( + client.xack(string_key, groupName, [stream_id1_0]), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "object encoding test_%p", + `lmpop test_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { - const string_key = uuidv4(); - const list_key = uuidv4(); - const hashtable_key = uuidv4(); - const intset_key = uuidv4(); - const set_listpack_key = uuidv4(); - const hash_hashtable_key = uuidv4(); - const hash_listpack_key = uuidv4(); - const skiplist_key = uuidv4(); - const zset_listpack_key = uuidv4(); - const stream_key = uuidv4(); - const non_existing_key = uuidv4(); - const versionLessThan7 = - await checkIfServerVersionLessThan("7.0.0"); - const versionLessThan72 = - await checkIfServerVersionLessThan("7.2.0"); + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) { + return; + } - expect(await client.objectEncoding(non_existing_key)).toEqual( - null, - ); + const key1 = "{key}" + uuidv4(); + const key2 = "{key}" + uuidv4(); + const nonListKey = uuidv4(); + const singleKeyArray = [key1]; + const multiKeyArray = [key2, key1]; + const count = 1; + const lpushArgs = ["one", "two", "three", "four", "five"]; + const expected = { [key1]: ["five"] }; + const expected2 = { [key2]: ["one", "two"] }; + + // nothing to be popped + expect( + await client.lmpop( + singleKeyArray, + ListDirection.LEFT, + count, + ), + ).toBeNull(); - checkSimple( - await client.set( - string_key, - "a really loooooooooooooooooooooooooooooooooooooooong value", + // pushing to the arrays to be popped + expect(await client.lpush(key1, lpushArgs)).toEqual(5); + expect(await client.lpush(key2, lpushArgs)).toEqual(5); + + // checking correct result from popping + expect( + await client.lmpop(singleKeyArray, ListDirection.LEFT), + ).toEqual(expected); + + // popping multiple elements from the right + expect( + await client.lmpop(multiKeyArray, ListDirection.RIGHT, 2), + ).toEqual(expected2); + + // Key exists, but is not a set + expect(await client.set(nonListKey, "lmpop")).toBe("OK"); + await expect( + client.lmpop([nonListKey], ListDirection.RIGHT), + ).rejects.toThrow(RequestError); + + // Test with single binary key array as input + const key3 = "{key}" + uuidv4(); + const singleKeyArrayWithKey3 = [Buffer.from(key3)]; + + // pushing to the arrays to be popped + expect(await client.lpush(key3, lpushArgs)).toEqual(5); + const expectedWithKey3 = { [key3]: ["five"] }; + + // checking correct result from popping + expect( + await client.lmpop( + singleKeyArrayWithKey3, + ListDirection.LEFT, ), - ).toEqual("OK"); + ).toEqual(expectedWithKey3); - checkSimple(await client.objectEncoding(string_key)).toEqual( - "raw", - ); + // test with multiple binary keys array as input + const key4 = "{key}" + uuidv4(); + const multiKeyArrayWithKey3AndKey4 = [ + Buffer.from(key4), + Buffer.from(key3), + ]; + + // pushing to the arrays to be popped + expect(await client.lpush(key4, lpushArgs)).toEqual(5); + const expectedWithKey4 = { [key4]: ["one", "two"] }; + + // checking correct result from popping + expect( + await client.lmpop( + multiKeyArrayWithKey3AndKey4, + ListDirection.RIGHT, + 2, + ), + ).toEqual(expectedWithKey4); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `blmpop test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) { + return; + } + + const key1 = "{key}" + uuidv4(); + const key2 = "{key}" + uuidv4(); + const nonListKey = uuidv4(); + const singleKeyArray = [key1]; + const multiKeyArray = [key2, key1]; + const count = 1; + const lpushArgs = ["one", "two", "three", "four", "five"]; + const expected = { [key1]: ["five"] }; + const expected2 = { [key2]: ["one", "two"] }; + + // nothing to be popped + expect( + await client.blmpop( + singleKeyArray, + ListDirection.LEFT, + 0.1, + count, + ), + ).toBeNull(); + + // pushing to the arrays to be popped + expect(await client.lpush(key1, lpushArgs)).toEqual(5); + expect(await client.lpush(key2, lpushArgs)).toEqual(5); + + // checking correct result from popping + expect( + await client.blmpop( + singleKeyArray, + ListDirection.LEFT, + 0.1, + ), + ).toEqual(expected); + + // popping multiple elements from the right + expect( + await client.blmpop( + multiKeyArray, + ListDirection.RIGHT, + 0.1, + 2, + ), + ).toEqual(expected2); - checkSimple(await client.set(string_key, "2")).toEqual("OK"); - checkSimple(await client.objectEncoding(string_key)).toEqual( - "int", - ); + // Key exists, but is not a set + expect(await client.set(nonListKey, "blmpop")).toBe("OK"); + await expect( + client.blmpop([nonListKey], ListDirection.RIGHT, 0.1, 1), + ).rejects.toThrow(RequestError); - checkSimple(await client.set(string_key, "value")).toEqual( - "OK", - ); - checkSimple(await client.objectEncoding(string_key)).toEqual( - "embstr", - ); + // Test with single binary key array as input + const key3 = "{key}" + uuidv4(); + const singleKeyArrayWithKey3 = [Buffer.from(key3)]; - expect(await client.lpush(list_key, ["1"])).toEqual(1); + // pushing to the arrays to be popped + expect(await client.lpush(key3, lpushArgs)).toEqual(5); + const expectedWithKey3 = { [key3]: ["five"] }; - if (versionLessThan72) { - checkSimple(await client.objectEncoding(list_key)).toEqual( - "quicklist", - ); - } else { - checkSimple(await client.objectEncoding(list_key)).toEqual( - "listpack", - ); - } + // checking correct result from popping + expect( + await client.blmpop( + singleKeyArrayWithKey3, + ListDirection.LEFT, + 0.1, + ), + ).toEqual(expectedWithKey3); - // The default value of set-max-intset-entries is 512 - for (let i = 0; i < 513; i++) { - expect( - await client.sadd(hashtable_key, [String(i)]), - ).toEqual(1); - } + // test with multiple binary keys array as input + const key4 = "{key}" + uuidv4(); + const multiKeyArrayWithKey3AndKey4 = [ + Buffer.from(key4), + Buffer.from(key3), + ]; - checkSimple(await client.objectEncoding(hashtable_key)).toEqual( - "hashtable", - ); + // pushing to the arrays to be popped + expect(await client.lpush(key4, lpushArgs)).toEqual(5); + const expectedWithKey4 = { [key4]: ["one", "two"] }; - expect(await client.sadd(intset_key, ["1"])).toEqual(1); - checkSimple(await client.objectEncoding(intset_key)).toEqual( - "intset", - ); + // checking correct result from popping + expect( + await client.blmpop( + multiKeyArrayWithKey3AndKey4, + ListDirection.RIGHT, + 0.1, + 2, + ), + ).toEqual(expectedWithKey4); + }, protocol); + }, + config.timeout, + ); - expect(await client.sadd(set_listpack_key, ["foo"])).toEqual(1); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `xgroupCreateConsumer and xgroupDelConsumer test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const nonExistentKey = uuidv4(); + const stringKey = uuidv4(); + const groupName = uuidv4(); + const consumer = uuidv4(); + const streamId0 = "0"; - if (versionLessThan72) { - checkSimple( - await client.objectEncoding(set_listpack_key), - ).toEqual("hashtable"); - } else { - checkSimple( - await client.objectEncoding(set_listpack_key), - ).toEqual("listpack"); - } + // create group and consumer for the group + expect( + await client.xgroupCreate(key, groupName, streamId0, { + mkStream: true, + }), + ).toEqual("OK"); + expect( + await client.xgroupCreateConsumer(key, groupName, consumer), + ).toEqual(true); - // The default value of hash-max-listpack-entries is 512 - for (let i = 0; i < 513; i++) { - expect( - await client.hset(hash_hashtable_key, { - [String(i)]: "2", - }), - ).toEqual(1); - } + // attempting to create/delete a consumer for a group that does not exist results in a NOGROUP request error + await expect( + client.xgroupCreateConsumer( + key, + "nonExistentGroup", + consumer, + ), + ).rejects.toThrow(RequestError); + await expect( + client.xgroupDelConsumer(key, "nonExistentGroup", consumer), + ).rejects.toThrow(RequestError); - checkSimple( - await client.objectEncoding(hash_hashtable_key), - ).toEqual("hashtable"); + // attempt to create consumer for group again + expect( + await client.xgroupCreateConsumer(key, groupName, consumer), + ).toEqual(false); + // attempting to delete a consumer that has not been created yet returns 0 expect( - await client.hset(hash_listpack_key, { "1": "2" }), - ).toEqual(1); + await client.xgroupDelConsumer( + key, + groupName, + "nonExistentConsumer", + ), + ).toEqual(0); - if (versionLessThan7) { - checkSimple( - await client.objectEncoding(hash_listpack_key), - ).toEqual("ziplist"); - } else { - checkSimple( - await client.objectEncoding(hash_listpack_key), - ).toEqual("listpack"); - } + // Add two stream entries + const streamid1: GlideString | null = await client.xadd(key, [ + ["field1", "value1"], + ]); + expect(streamid1).not.toBeNull(); - // The default value of zset-max-listpack-entries is 128 - for (let i = 0; i < 129; i++) { - expect( - await client.zadd(skiplist_key, { [String(i)]: 2.0 }), - ).toEqual(1); - } + // testing binary parameters + const streamid2 = await client.xadd(Buffer.from(key), [ + [Buffer.from("field2"), Buffer.from("value2")], + ]); + expect(streamid2).not.toBeNull(); - checkSimple(await client.objectEncoding(skiplist_key)).toEqual( - "skiplist", - ); + // read the entire stream for the consumer and mark messages as pending + expect( + await client.xreadgroup(groupName, consumer, { + [key]: ">", + }), + ).toEqual({ + [key]: { + [streamid1 as string]: [["field1", "value1"]], + [streamid2 as string]: [["field2", "value2"]], + }, + }); + // delete one of the streams & testing binary parameters expect( - await client.zadd(zset_listpack_key, { "1": 2.0 }), - ).toEqual(1); + await client.xgroupDelConsumer( + Buffer.from(key), + Buffer.from(groupName), + Buffer.from(consumer), + ), + ).toEqual(2); - if (versionLessThan7) { - checkSimple( - await client.objectEncoding(zset_listpack_key), - ).toEqual("ziplist"); - } else { - checkSimple( - await client.objectEncoding(zset_listpack_key), - ).toEqual("listpack"); - } + // attempting to call XGROUP CREATECONSUMER or XGROUP DELCONSUMER with a non-existing key should raise an error + await expect( + client.xgroupCreateConsumer( + nonExistentKey, + groupName, + consumer, + ), + ).rejects.toThrow(RequestError); + await expect( + client.xgroupDelConsumer( + nonExistentKey, + groupName, + consumer, + ), + ).rejects.toThrow(RequestError); - expect( - await client.xadd(stream_key, [["field", "value"]]), - ).not.toBeNull(); - checkSimple(await client.objectEncoding(stream_key)).toEqual( - "stream", - ); + // key exists, but it is not a stream + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.xgroupCreateConsumer(stringKey, groupName, consumer), + ).rejects.toThrow(RequestError); + await expect( + client.xgroupDelConsumer(stringKey, groupName, consumer), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "object freq test_%p", + `xgroupCreate and xgroupDestroy test_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { + await runTest(async (client: BaseClient, cluster) => { const key = uuidv4(); - const nonExistingKey = uuidv4(); - const maxmemoryPolicyKey = "maxmemory-policy"; - const config = await client.configGet([maxmemoryPolicyKey]); - const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); + const nonExistentKey = uuidv4(); + const stringKey = uuidv4(); + const groupName1 = uuidv4(); + const groupName2 = uuidv4(); + const streamId = "0-1"; - try { - expect( - await client.configSet({ - [maxmemoryPolicyKey]: "allkeys-lfu", + // trying to create a consumer group for a non-existing stream without the "MKSTREAM" arg results in error + await expect( + client.xgroupCreate(nonExistentKey, groupName1, streamId), + ).rejects.toThrow(RequestError); + + // calling with the "MKSTREAM" arg should create the new stream automatically + expect( + await client.xgroupCreate(key, groupName1, streamId, { + mkStream: true, + }), + ).toEqual("OK"); + + // invalid arg - group names must be unique, but group_name1 already exists + await expect( + client.xgroupCreate(key, groupName1, streamId), + ).rejects.toThrow(RequestError); + + // Invalid stream ID format + await expect( + client.xgroupCreate( + key, + groupName2, + "invalid_stream_id_format", + ), + ).rejects.toThrow(RequestError); + + expect(await client.xgroupDestroy(key, groupName1)).toEqual( + true, + ); + // calling xgroup_destroy again returns False because the group was already destroyed above + expect(await client.xgroupDestroy(key, groupName1)).toEqual( + false, + ); + // calling again with binary parameters, expecting the same result + expect( + await client.xgroupDestroy( + Buffer.from(key), + Buffer.from(groupName1), + ), + ).toEqual(false); + + // attempting to destroy a group for a non-existing key should raise an error + await expect( + client.xgroupDestroy(nonExistentKey, groupName1), + ).rejects.toThrow(RequestError); + + // "ENTRIESREAD" option was added in Valkey 7.0.0 + if (cluster.checkIfServerVersionLessThan("7.0.0")) { + await expect( + client.xgroupCreate(key, groupName1, streamId, { + entriesRead: "10", }), - ).toEqual("OK"); - expect(await client.objectFreq(nonExistingKey)).toEqual( - null, - ); - expect(await client.set(key, "foobar")).toEqual("OK"); - expect(await client.objectFreq(key)).toBeGreaterThanOrEqual( - 0, - ); - } finally { + ).rejects.toThrow(RequestError); + } else { expect( - await client.configSet({ - [maxmemoryPolicyKey]: maxmemoryPolicy, + await client.xgroupCreate(key, groupName1, streamId, { + entriesRead: "10", }), ).toEqual("OK"); + + // invalid entries_read_id - cannot be the zero ("0-0") ID + await expect( + client.xgroupCreate(key, groupName1, streamId, { + entriesRead: "0-0", + }), + ).rejects.toThrow(RequestError); } + + // key exists, but it is not a stream + expect(await client.set(stringKey, "foo")).toEqual("OK"); + await expect( + client.xgroupCreate(stringKey, groupName1, streamId, { + mkStream: true, + }), + ).rejects.toThrow(RequestError); + await expect( + client.xgroupDestroy(stringKey, groupName1), + ).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - "object idletime test_%p", + "check that blocking commands never time out %p", async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = uuidv4(); - const nonExistingKey = uuidv4(); - const maxmemoryPolicyKey = "maxmemory-policy"; - const config = await client.configGet([maxmemoryPolicyKey]); - const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); + await runTest(async (client: BaseClient, cluster) => { + const key1 = "{blocking}-1-" + uuidv4(); + const key2 = "{blocking}-2-" + uuidv4(); + const key3 = "{blocking}-3-" + uuidv4(); // stream + const keyz = [key1, key2]; + + // create a group and a stream, so `xreadgroup` won't fail on missing group + await client.xgroupCreate(key3, "group", "0", { + mkStream: true, + }); - try { - expect( - await client.configSet({ - // OBJECT IDLETIME requires a non-LFU maxmemory-policy - [maxmemoryPolicyKey]: "allkeys-random", - }), - ).toEqual("OK"); - expect(await client.objectIdletime(nonExistingKey)).toEqual( - null, - ); - expect(await client.set(key, "foobar")).toEqual("OK"); + const promiseList: [string, Promise][] = [ + ["bzpopmax", client.bzpopmax(keyz, 0)], + ["bzpopmin", client.bzpopmin(keyz, 0)], + ["blpop", client.blpop(keyz, 0)], + ["brpop", client.brpop(keyz, 0)], + ["xread", client.xread({ [key3]: "0-0" }, { block: 0 })], + [ + "xreadgroup", + client.xreadgroup( + "group", + "consumer", + { [key3]: "0-0" }, + { block: 0 }, + ), + ], + ["wait", client.wait(42, 0)], + ]; - await wait(2000); + if (!cluster.checkIfServerVersionLessThan("6.2.0")) { + promiseList.push([ + "blmove", + client.blmove( + key1, + key2, + ListDirection.LEFT, + ListDirection.LEFT, + 0, + ), + ]); + } + + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + promiseList.push( + ["blmpop", client.blmpop(keyz, ListDirection.LEFT, 0)], + ["bzmpop", client.bzmpop(keyz, ScoreFilter.MAX, 0)], + ); + } - expect(await client.objectIdletime(key)).toBeGreaterThan(0); + try { + for (const [name, promise] of promiseList) { + const timeoutPromise = new Promise((resolve) => { + setTimeout(resolve, 500, "timeOutPromiseWins"); + }); + // client has default request timeout 250 ms, we run all commands with infinite blocking + // we expect that all commands will still await for the response even after 500 ms + expect( + await Promise.race([ + promise.finally(() => + fail(`${name} didn't block infintely`), + ), + timeoutPromise, + ]), + ).toEqual("timeOutPromiseWins"); + } } finally { - expect( - await client.configSet({ - [maxmemoryPolicyKey]: maxmemoryPolicy, - }), - ).toEqual("OK"); + client.close(); } }, protocol); }, config.timeout, ); - function wait(numMilliseconds: number) { - return new Promise((resolve) => { - setTimeout(resolve, numMilliseconds); - }); - } - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `object refcount test_%p`, + `getex test_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = `{key}:${uuidv4()}`; - const nonExistingKey = `{key}:${uuidv4()}`; + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) { + return; + } - expect(await client.objectRefcount(nonExistingKey)).toBeNull(); - expect(await client.set(key, "foo")).toEqual("OK"); - expect(await client.objectRefcount(key)).toBeGreaterThanOrEqual( - 1, + const key1 = "{key}" + uuidv4(); + const key2 = "{key}" + uuidv4(); + const value = uuidv4(); + + expect(await client.set(key1, value)).toBe("OK"); + expect(await client.getex(key1)).toEqual(value); + expect(await client.ttl(key1)).toBe(-1); + + expect( + await client.getex(key1, { + expiry: { + type: TimeUnit.Seconds, + duration: 15, + }, + }), + ).toEqual(value); + // test the binary option + expect( + await client.getex(Buffer.from(key1), { + expiry: { + type: TimeUnit.Seconds, + duration: 1, + }, + }), + ).toEqual(value); + expect(await client.ttl(key1)).toBeGreaterThan(0); + expect(await client.getex(key1, { expiry: "persist" })).toEqual( + value, ); + expect(await client.ttl(key1)).toBe(-1); + + // non existent key + expect(await client.getex(key2)).toBeNull(); + + // invalid time measurement + await expect( + client.getex(key1, { + expiry: { + type: TimeUnit.Seconds, + duration: -10, + }, + }), + ).rejects.toThrow(RequestError); + + // Key exists, but is not a string + expect(await client.sadd(key2, ["a"])).toBe(1); + await expect(client.getex(key2)).rejects.toThrow(RequestError); }, protocol); }, config.timeout, ); } -export function runCommonTests(config: { - init: () => Promise<{ context: Context; client: Client }>; - close: (context: Context, testSucceeded: boolean) => void; +export function runCommonTests(config: { + init: () => Promise<{ client: Client }>; + close: (testSucceeded: boolean) => void; timeout?: number; }) { const runTest = async (test: (client: Client) => Promise) => { - const { context, client } = await config.init(); + const { client } = await config.init(); let testSucceeded = false; try { await test(client); testSucceeded = true; } finally { - config.close(context, testSucceeded); + config.close(testSucceeded); } }; @@ -3684,7 +11418,7 @@ export function runCommonTests(config: { const value = "שלום hello 汉字"; await client.set(key, value); const result = await client.get(key); - checkSimple(result).toEqual(value); + expect(result).toEqual(value); }); }, config.timeout, @@ -3696,7 +11430,7 @@ export function runCommonTests(config: { await runTest(async (client: Client) => { const result = await client.get(uuidv4()); - checkSimple(result).toEqual(null); + expect(result).toEqual(null); }); }, config.timeout, @@ -3710,7 +11444,7 @@ export function runCommonTests(config: { await client.set(key, ""); const result = await client.get(key); - checkSimple(result).toEqual(""); + expect(result).toEqual(""); }); }, config.timeout, @@ -3737,7 +11471,7 @@ export function runCommonTests(config: { await client.set(key, value); const result = await client.get(key); - checkSimple(result).toEqual(value); + expect(result).toEqual(value); }); }, config.timeout, @@ -3752,7 +11486,7 @@ export function runCommonTests(config: { await GetAndSetRandomValue(client); } else { const result = await client.get(uuidv4()); - checkSimple(result).toEqual(null); + expect(result).toEqual(null); } }; diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index 5872a4bdee..7136c043ab 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -2,27 +2,42 @@ * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ -import { beforeAll, expect } from "@jest/globals"; +import { expect } from "@jest/globals"; import { exec } from "child_process"; import parseArgs from "minimist"; +import { gte } from "semver"; import { v4 as uuidv4 } from "uuid"; import { BaseClient, BaseClientConfiguration, + BitFieldGet, + BitFieldSet, + BitOffset, + BitOffsetMultiplier, + BitmapIndexType, + BitwiseOperation, ClusterTransaction, + FlushMode, + FunctionListResponse, + FunctionStatsSingleResponse, + GeoUnit, + GeospatialData, GlideClient, GlideClusterClient, + GlideString, + InfBoundary, InsertPosition, - Logger, + ListDirection, ProtocolVersion, ReturnType, + ReturnTypeMap, + ScoreFilter, + SignedEncoding, + SortOrder, + TimeUnit, Transaction, + UnsignedEncoding, } from ".."; -import { checkIfServerVersionLessThan } from "./SharedTests"; - -beforeAll(() => { - Logger.init("info"); -}); /* eslint-disable @typescript-eslint/no-explicit-any */ function intoArrayInternal(obj: any, builder: Array) { @@ -30,6 +45,8 @@ function intoArrayInternal(obj: any, builder: Array) { builder.push("null"); } else if (typeof obj === "string") { builder.push(obj); + } else if (typeof obj === "number") { + builder.push(obj.toPrecision(3)); } else if (obj instanceof Uint8Array) { builder.push(obj.toString()); } else if (obj instanceof Array) { @@ -114,8 +131,8 @@ export function checkSimple(left: any): Checker { } export type Client = { - set: (key: string, value: string) => Promise; - get: (key: string) => Promise; + set: (key: string, value: string) => Promise; + get: (key: string) => Promise; }; export async function GetAndSetRandomValue(client: Client) { @@ -201,6 +218,95 @@ export function getFirstResult( return Object.values(res).at(0); } +// TODO use matcher instead of predicate +/** Check a multi-node response from a cluster. */ +export function checkClusterMultiNodeResponse( + res: object, + predicate: (value: ReturnType) => void, +) { + for (const nodeResponse of Object.values(res)) { + predicate(nodeResponse); + } +} + +/** Check a response from a cluster. Response could be either single-node (value) or multi-node (string-value map). */ +export function checkClusterResponse( + res: object, + singleNodeRoute: boolean, + predicate: (value: ReturnType) => void, +) { + if (singleNodeRoute) predicate(res as ReturnType); + else checkClusterMultiNodeResponse(res, predicate); +} + +/** Generate a String of LUA library code. */ +export function generateLuaLibCode( + libName: string, + functions: Map, + readonly: boolean, +): string { + let code = `#!lua name=${libName}\n`; + + for (const [functionName, functionBody] of functions) { + code += `redis.register_function{ function_name = '${functionName}', callback = function(keys, args) ${functionBody} end`; + if (readonly) code += ", flags = { 'no-writes' }"; + code += " }\n"; + } + + return code; +} + +/** + * Create a lua lib with a function which runs an endless loop up to timeout sec. + * Execution takes at least 5 sec regardless of the timeout configured. + */ +export function createLuaLibWithLongRunningFunction( + libName: string, + funcName: string, + timeout: number, + readOnly: boolean, +): string { + const code = + "#!lua name=$libName\n" + + "local function $libName_$funcName(keys, args)\n" + + " local started = tonumber(redis.pcall('time')[1])\n" + + // fun fact - redis does no write if 'no-writes' flag is set + " redis.pcall('set', keys[1], 42)\n" + + " while (true) do\n" + + " local now = tonumber(redis.pcall('time')[1])\n" + + " if now > started + $timeout then\n" + + " return 'Timed out $timeout sec'\n" + + " end\n" + + " end\n" + + " return 'OK'\n" + + "end\n" + + "redis.register_function{\n" + + "function_name='$funcName',\n" + + "callback=$libName_$funcName,\n" + + (readOnly ? "flags={ 'no-writes' }\n" : "") + + "}"; + return code + .replaceAll("$timeout", timeout.toString()) + .replaceAll("$funcName", funcName) + .replaceAll("$libName", libName); +} + +export async function waitForNotBusy(client: GlideClusterClient | GlideClient) { + // If function wasn't killed, and it didn't time out - it blocks the server and cause rest test to fail. + let isBusy = true; + + do { + try { + await client.functionKill(); + } catch (err) { + // should throw `notbusy` error, because the function should be killed before + if ((err as Error).message.toLowerCase().includes("notbusy")) { + isBusy = false; + } + } + } while (isBusy); +} + /** * Parses the command-line arguments passed to the Node.js process. * @@ -232,7 +338,7 @@ export async function testTeardown( export const getClientConfigurationOption = ( addresses: [string, number][], protocol: ProtocolVersion, - timeout?: number, + configOverrides?: Partial, ): BaseClientConfiguration => { return { addresses: addresses.map(([host, port]) => ({ @@ -240,7 +346,7 @@ export const getClientConfigurationOption = ( port, })), protocol, - ...(timeout && { requestTimeout: timeout }), + ...configOverrides, }; }; @@ -251,7 +357,9 @@ export async function flushAndCloseClient( ) { await testTeardown( cluster_mode, - getClientConfigurationOption(addresses, ProtocolVersion.RESP3, 2000), + getClientConfigurationOption(addresses, ProtocolVersion.RESP3, { + requestTimeout: 2000, + }), ); // some tests don't initialize a client @@ -292,13 +400,258 @@ export function compareMaps( return JSON.stringify(map) == JSON.stringify(map2); } +/** + * Validate whether `FUNCTION LIST` response contains required info. + * + * @param response - The response from server. + * @param libName - Expected library name. + * @param functionDescriptions - Expected function descriptions. Key - function name, value - description. + * @param functionFlags - Expected function flags. Key - function name, value - flags set. + * @param libCode - Expected library to check if given. + */ +export function checkFunctionListResponse( + response: FunctionListResponse, + libName: string, + functionDescriptions: Map, + functionFlags: Map, + libCode?: string, +) { + // TODO rework after #1953 https://github.com/valkey-io/valkey-glide/pull/1953 + expect(response.length).toBeGreaterThan(0); + let hasLib = false; + + for (const lib of response) { + hasLib = lib["library_name"] == libName; + + if (hasLib) { + const functions = lib["functions"]; + expect(functions.length).toEqual(functionDescriptions.size); + + for (const functionData of functions) { + const functionInfo = functionData as Record< + string, + string | string[] + >; + const name = functionInfo["name"] as string; + const flags = functionInfo["flags"] as string[]; + expect(functionInfo["description"]).toEqual( + functionDescriptions.get(name), + ); + + expect(flags).toEqual(functionFlags.get(name)); + } + + if (libCode) { + expect(lib["library_code"]).toEqual(libCode); + } + + break; + } + } + + expect(hasLib).toBeTruthy(); +} + +/** + * Validate whether `FUNCTION STATS` response contains required info. + * + * @param response - The response from server. + * @param runningFunction - Command line of running function expected. Empty, if nothing expected. + * @param libCount - Expected libraries count. + * @param functionCount - Expected functions count. + */ +export function checkFunctionStatsResponse( + response: FunctionStatsSingleResponse, + runningFunction: GlideString[], + libCount: number, + functionCount: number, +) { + if (response.running_script === null && runningFunction.length > 0) { + fail("No running function info"); + } + + if (response.running_script !== null && runningFunction.length == 0) { + fail( + "Unexpected running function info: " + + (response.running_script.command as string[]).join(" "), + ); + } + + if (response.running_script !== null) { + expect(response.running_script.command).toEqual(runningFunction); + // command line format is: + // fcall|fcall_ro * * + expect(response.running_script.name).toEqual(runningFunction[1]); + } + + expect(response.engines).toEqual({ + LUA: { libraries_count: libCount, functions_count: functionCount }, + }); +} + +/** + * Check transaction response. + * @param response - Transaction result received from `exec` call. + * @param expectedResponseData - Expected result data from {@link transactionTest}. + */ +export function validateTransactionResponse( + response: ReturnType[] | null, + expectedResponseData: [string, ReturnType][], +) { + const failedChecks: string[] = []; + + for (let i = 0; i < expectedResponseData.length; i++) { + const [testName, expectedResponse] = expectedResponseData[i]; + + try { + expect(response?.[i]).toEqual(expectedResponse); + } catch { + const expected = + expectedResponse instanceof Map + ? JSON.stringify(Array.from(expectedResponse.entries())) + : JSON.stringify(expectedResponse); + const actual = + response?.[i] instanceof Map + ? JSON.stringify( + Array.from( + (response?.[i] as ReturnTypeMap)?.entries(), + ), + ) + : JSON.stringify(response?.[i]); + failedChecks.push( + `${testName} failed, expected <${expected}>, actual <${actual}>`, + ); + } + } + + if (failedChecks.length > 0) { + throw new Error( + "Checks failed in transaction response:\n" + + failedChecks.join("\n"), + ); + } +} + +/** + * Populates a transaction with commands to test the decodable commands with various default decoders. + * @param baseTransaction - A transaction. + * @param valueEncodedResponse - Represents the encoded response of "value" to compare + * @returns Array of tuples, where first element is a test name/description, second - expected return value. + */ +export async function encodableTransactionTest( + baseTransaction: Transaction | ClusterTransaction, + valueEncodedResponse: ReturnType, +): Promise<[string, ReturnType][]> { + const key = "{key}" + uuidv4(); // string + const value = "value"; + // array of tuples - first element is test name/description, second - expected return value + const responseData: [string, ReturnType][] = []; + + baseTransaction.set(key, value); + responseData.push(["set(key, value)", "OK"]); + baseTransaction.get(key); + responseData.push(["get(key)", valueEncodedResponse]); + + return responseData; +} + +/** + * Populates a transaction with commands to test the decoded response. + * @param baseTransaction - A transaction. + * @returns Array of tuples, where first element is a test name/description, second - expected return value. + */ +export async function encodedTransactionTest( + baseTransaction: Transaction | ClusterTransaction, +): Promise<[string, ReturnType][]> { + const key1 = "{key}" + uuidv4(); // string + const key2 = "{key}" + uuidv4(); // string + const key = "dumpKey"; + const dumpResult = Buffer.from([ + 0, 5, 118, 97, 108, 117, 101, 11, 0, 232, 41, 124, 75, 60, 53, 114, 231, + ]); + const value = "value"; + const valueEncoded = Buffer.from(value); + // array of tuples - first element is test name/description, second - expected return value + const responseData: [string, ReturnType][] = []; + + baseTransaction.set(key1, value); + responseData.push(["set(key1, value)", "OK"]); + baseTransaction.set(key2, value); + responseData.push(["set(key2, value)", "OK"]); + baseTransaction.get(key1); + responseData.push(["get(key1)", valueEncoded]); + baseTransaction.get(key2); + responseData.push(["get(key2)", valueEncoded]); + + baseTransaction.set(key, value); + responseData.push(["set(key, value)", "OK"]); + baseTransaction.customCommand(["DUMP", key]); + responseData.push(['customCommand(["DUMP", key])', dumpResult]); + baseTransaction.del([key]); + responseData.push(["del(key)", 1]); + baseTransaction.get(key); + responseData.push(["get(key)", null]); + baseTransaction.customCommand(["RESTORE", key, "0", dumpResult]); + responseData.push([ + 'customCommand(["RESTORE", key, "0", dumpResult])', + "OK", + ]); + baseTransaction.get(key); + responseData.push(["get(key)", valueEncoded]); + + return responseData; +} + +/** Populates a transaction with dump and restore commands + * + * @param baseTransaction - A transaction + * @param valueResponse - Represents the encoded response of "value" to compare + * @returns Array of tuples, where first element is a test name/description, second - expected return value. + */ +export async function DumpAndRestureTest( + baseTransaction: Transaction, + valueResponse: GlideString, +): Promise<[string, ReturnType][]> { + const key = "dumpKey"; + const dumpResult = Buffer.from([ + 0, 5, 118, 97, 108, 117, 101, 11, 0, 232, 41, 124, 75, 60, 53, 114, 231, + ]); + const value = "value"; + // array of tuples - first element is test name/description, second - expected return value + const responseData: [string, ReturnType][] = []; + + baseTransaction.set(key, value); + responseData.push(["set(key, value)", "OK"]); + baseTransaction.customCommand(["DUMP", key]); + responseData.push(['customCommand(["DUMP", key])', dumpResult]); + baseTransaction.del([key]); + responseData.push(["del(key)", 1]); + baseTransaction.get(key); + responseData.push(["get(key)", null]); + baseTransaction.customCommand(["RESTORE", key, "0", dumpResult]); + responseData.push([ + 'customCommand(["RESTORE", key, "0", dumpResult])', + "OK", + ]); + baseTransaction.get(key); + responseData.push(["get(key)", valueResponse]); + + return responseData; +} + +/** + * Populates a transaction with commands to test. + * @param baseTransaction - A transaction. + * @returns Array of tuples, where first element is a test name/description, second - expected return value. + */ export async function transactionTest( baseTransaction: Transaction | ClusterTransaction, -): Promise { - const key1 = "{key}" + uuidv4(); - const key2 = "{key}" + uuidv4(); - const key3 = "{key}" + uuidv4(); - const key4 = "{key}" + uuidv4(); + version: string, +): Promise<[string, ReturnType][]> { + const key1 = "{key}" + uuidv4(); // string + const key2 = "{key}" + uuidv4(); // string + const key3 = "{key}" + uuidv4(); // string + const key4 = "{key}" + uuidv4(); // hash const key5 = "{key}" + uuidv4(); const key6 = "{key}" + uuidv4(); const key7 = "{key}" + uuidv4(); @@ -309,117 +662,304 @@ export async function transactionTest( const key12 = "{key}" + uuidv4(); const key13 = "{key}" + uuidv4(); const key14 = "{key}" + uuidv4(); // sorted set + const key15 = "{key}" + uuidv4(); // list + const key16 = "{key}" + uuidv4(); // list + const key17 = "{key}" + uuidv4(); // bitmap + const key18 = "{key}" + uuidv4(); // Geospatial Data/ZSET + const key19 = "{key}" + uuidv4(); // bitmap + const key20 = "{key}" + uuidv4(); // list + const key21 = "{key}" + uuidv4(); // list for sort + const key22 = "{key}" + uuidv4(); // list for sort + const key23 = "{key}" + uuidv4(); // zset random + const key24 = "{key}" + uuidv4(); // list value + const key25 = "{key}" + uuidv4(); // Geospatial Data/ZSET + const key26 = "{key}" + uuidv4(); // sorted set + const key27 = "{key}" + uuidv4(); // sorted set const field = uuidv4(); const value = uuidv4(); - const args: ReturnType[] = []; + const groupName1 = uuidv4(); + const groupName2 = uuidv4(); + const consumer = uuidv4(); + // array of tuples - first element is test name/description, second - expected return value + const responseData: [string, ReturnType][] = []; + + baseTransaction.publish("test_message", key1); + responseData.push(['publish("test_message", key1)', 0]); + baseTransaction.pubsubChannels(); + responseData.push(["pubsubChannels()", []]); + baseTransaction.pubsubNumPat(); + responseData.push(["pubsubNumPat()", 0]); + baseTransaction.pubsubNumSub(); + responseData.push(["pubsubNumSub()", {}]); + + baseTransaction.flushall(); + responseData.push(["flushall()", "OK"]); + baseTransaction.flushall(FlushMode.SYNC); + responseData.push(["flushall(FlushMode.SYNC)", "OK"]); + baseTransaction.flushdb(); + responseData.push(["flushdb()", "OK"]); + baseTransaction.flushdb(FlushMode.SYNC); + responseData.push(["flushdb(FlushMode.SYNC)", "OK"]); + baseTransaction.dbsize(); + responseData.push(["dbsize()", 0]); + baseTransaction.set(key1, "foo"); + responseData.push(['set(key1, "bar")', "OK"]); + baseTransaction.set(key1, "bar", { returnOldValue: true }); + responseData.push(['set(key1, "bar", {returnOldValue: true})', "foo"]); + + if (gte(version, "6.2.0")) { + baseTransaction.getex(key1); + responseData.push(["getex(key1)", "bar"]); + baseTransaction.getex(key1, { type: TimeUnit.Seconds, duration: 1 }); + responseData.push([ + 'getex(key1, {expiry: { type: "seconds", count: 1 }})', + "bar", + ]); + } + + baseTransaction.randomKey(); + responseData.push(["randomKey()", key1]); + baseTransaction.getrange(key1, 0, -1); + responseData.push(["getrange(key1, 0, -1)", "bar"]); + baseTransaction.getdel(key1); + responseData.push(["getdel(key1)", "bar"]); baseTransaction.set(key1, "bar"); - args.push("OK"); + responseData.push(['set(key1, "bar")', "OK"]); baseTransaction.objectEncoding(key1); - args.push("embstr"); + responseData.push(["objectEncoding(key1)", "embstr"]); baseTransaction.type(key1); - args.push("string"); + responseData.push(["type(key1)", "string"]); baseTransaction.echo(value); - args.push(value); + responseData.push(["echo(value)", value]); baseTransaction.persist(key1); - args.push(false); - baseTransaction.set(key2, "baz", { - returnOldValue: true, - }); - args.push(null); + responseData.push(["persist(key1)", false]); + + if (gte(version, "7.0.0")) { + baseTransaction.expireTime(key1); + responseData.push(["expiretime(key1)", -1]); + + baseTransaction.pexpireTime(key1); + responseData.push(["pexpiretime(key1)", -1]); + } + + baseTransaction.set(key2, "baz", { returnOldValue: true }); + responseData.push(['set(key2, "baz", { returnOldValue: true })', null]); baseTransaction.customCommand(["MGET", key1, key2]); - args.push(["bar", "baz"]); + responseData.push(['customCommand(["MGET", key1, key2])', ["bar", "baz"]]); baseTransaction.mset({ [key3]: value }); - args.push("OK"); + responseData.push(["mset({ [key3]: value })", "OK"]); + baseTransaction.msetnx({ [key3]: value }); + responseData.push(["msetnx({ [key3]: value })", false]); baseTransaction.mget([key1, key2]); - args.push(["bar", "baz"]); + responseData.push(["mget([key1, key2])", ["bar", "baz"]]); baseTransaction.strlen(key1); - args.push(3); + responseData.push(["strlen(key1)", 3]); + baseTransaction.setrange(key1, 0, "GLIDE"); + responseData.push(["setrange(key1, 0, 'GLIDE')", 5]); baseTransaction.del([key1]); - args.push(1); - baseTransaction.hset(key4, { [field]: value }); - args.push(1); + responseData.push(["del([key1])", 1]); + baseTransaction.append(key1, "bar"); + responseData.push(["append(key1, value)", 3]); + baseTransaction.del([key1]); + responseData.push(["del([key1])", 1]); + baseTransaction.hset(key4, [{ field, value }]); + responseData.push(["hset(key4, { [field]: value })", 1]); + baseTransaction.hscan(key4, "0"); + responseData.push(['hscan(key4, "0")', ["0", [field, value]]]); + baseTransaction.hscan(key4, "0", { match: "*", count: 20 }); + responseData.push([ + 'hscan(key4, "0", {match: "*", count: 20})', + ["0", [field, value]], + ]); + baseTransaction.hstrlen(key4, field); + responseData.push(["hstrlen(key4, field)", value.length]); baseTransaction.hlen(key4); - args.push(1); + responseData.push(["hlen(key4)", 1]); + baseTransaction.hrandfield(key4); + responseData.push(["hrandfield(key4)", field]); + baseTransaction.hrandfieldCount(key4, -2); + responseData.push(["hrandfieldCount(key4, -2)", [field, field]]); + baseTransaction.hrandfieldWithValues(key4, 2); + responseData.push(["hrandfieldWithValues(key4, 2)", [[field, value]]]); baseTransaction.hsetnx(key4, field, value); - args.push(false); + responseData.push(["hsetnx(key4, field, value)", false]); baseTransaction.hvals(key4); - args.push([value]); + responseData.push(["hvals(key4)", [value]]); + baseTransaction.hkeys(key4); + responseData.push(["hkeys(key4)", [field]]); baseTransaction.hget(key4, field); - args.push(value); + responseData.push(["hget(key4, field)", value]); baseTransaction.hgetall(key4); - args.push({ [field]: value }); + responseData.push(["hgetall(key4)", { [field]: value }]); baseTransaction.hdel(key4, [field]); - args.push(1); + responseData.push(["hdel(key4, [field])", 1]); baseTransaction.hmget(key4, [field]); - args.push([null]); + responseData.push(["hmget(key4, [field])", [null]]); baseTransaction.hexists(key4, field); - args.push(false); + responseData.push(["hexists(key4, field)", false]); + baseTransaction.hrandfield(key4); + responseData.push(["hrandfield(key4)", null]); + baseTransaction.lpush(key5, [ field + "1", field + "2", field + "3", field + "4", ]); - args.push(4); + responseData.push(["lpush(key5, [1, 2, 3, 4])", 4]); + + if (gte("7.0.0", version)) { + baseTransaction.lpush(key24, [field + "1", field + "2"]); + responseData.push(["lpush(key22, [1, 2])", 2]); + baseTransaction.lmpop([key24], ListDirection.LEFT); + responseData.push([ + "lmpop([key22], ListDirection.LEFT)", + { [key24]: [field + "2"] }, + ]); + baseTransaction.lpush(key24, [field + "2"]); + responseData.push(["lpush(key22, [2])", 2]); + baseTransaction.blmpop([key24], ListDirection.LEFT, 0.1, 1); + responseData.push([ + "blmpop([key22], ListDirection.LEFT, 0.1, 1)", + { [key24]: [field + "2"] }, + ]); + } + baseTransaction.lpop(key5); - args.push(field + "4"); + responseData.push(["lpop(key5)", field + "4"]); baseTransaction.llen(key5); - args.push(3); + responseData.push(["llen(key5)", 3]); baseTransaction.lrem(key5, 1, field + "1"); - args.push(1); + responseData.push(['lrem(key5, 1, field + "1")', 1]); baseTransaction.ltrim(key5, 0, 1); - args.push("OK"); + responseData.push(["ltrim(key5, 0, 1)", "OK"]); baseTransaction.lset(key5, 0, field + "3"); - args.push("OK"); + responseData.push(['lset(key5, 0, field + "3")', "OK"]); baseTransaction.lrange(key5, 0, -1); - args.push([field + "3", field + "2"]); + responseData.push(["lrange(key5, 0, -1)", [field + "3", field + "2"]]); + + if (gte(version, "6.2.0")) { + baseTransaction.lmove( + key5, + key20, + ListDirection.LEFT, + ListDirection.LEFT, + ); + responseData.push([ + "lmove(key5, key20, ListDirection.LEFT, ListDirection.LEFT)", + field + "3", + ]); + + baseTransaction.blmove( + key20, + key5, + ListDirection.LEFT, + ListDirection.LEFT, + 3, + ); + responseData.push([ + "blmove(key20, key5, ListDirection.LEFT, ListDirection.LEFT, 3)", + field + "3", + ]); + } + baseTransaction.lpopCount(key5, 2); - args.push([field + "3", field + "2"]); + responseData.push(["lpopCount(key5, 2)", [field + "3", field + "2"]]); + baseTransaction.linsert( key5, InsertPosition.Before, "nonExistingPivot", "element", ); - args.push(0); + responseData.push(["linsert", 0]); baseTransaction.rpush(key6, [field + "1", field + "2", field + "3"]); - args.push(3); + responseData.push([ + 'rpush(key6, [field + "1", field + "2", field + "3"])', + 3, + ]); baseTransaction.lindex(key6, 0); - args.push(field + "1"); + responseData.push(["lindex(key6, 0)", field + "1"]); baseTransaction.rpop(key6); - args.push(field + "3"); + responseData.push(["rpop(key6)", field + "3"]); baseTransaction.rpopCount(key6, 2); - args.push([field + "2", field + "1"]); + responseData.push(["rpopCount(key6, 2)", [field + "2", field + "1"]]); + baseTransaction.rpushx(key15, ["_"]); // key15 is empty + responseData.push(['rpushx(key15, ["_"])', 0]); + baseTransaction.lpushx(key15, ["_"]); + responseData.push(['lpushx(key15, ["_"])', 0]); + baseTransaction.rpush(key16, [ + field + "1", + field + "1", + field + "2", + field + "3", + field + "3", + ]); + responseData.push(["rpush(key16, [1, 1, 2, 3, 3,])", 5]); + baseTransaction.lpos(key16, field + "1", { rank: 2 }); + responseData.push(['lpos(key16, field + "1", { rank: 2 })', 1]); + baseTransaction.lpos(key16, field + "1", { rank: 2, count: 0 }); + responseData.push(['lpos(key16, field + "1", { rank: 2, count: 0 })', [1]]); baseTransaction.sadd(key7, ["bar", "foo"]); - args.push(2); + responseData.push(['sadd(key7, ["bar", "foo"])', 2]); baseTransaction.sunionstore(key7, [key7, key7]); - args.push(2); + responseData.push(["sunionstore(key7, [key7, key7])", 2]); baseTransaction.sunion([key7, key7]); - args.push(new Set(["bar", "foo"])); + responseData.push(["sunion([key7, key7])", new Set(["bar", "foo"])]); baseTransaction.sinter([key7, key7]); - args.push(new Set(["bar", "foo"])); + responseData.push(["sinter([key7, key7])", new Set(["bar", "foo"])]); + + if (gte(version, "7.0.0")) { + baseTransaction.sintercard([key7, key7]); + responseData.push(["sintercard([key7, key7])", 2]); + baseTransaction.sintercard([key7, key7], 1); + responseData.push(["sintercard([key7, key7], 1)", 1]); + } + baseTransaction.sinterstore(key7, [key7, key7]); - args.push(2); + responseData.push(["sinterstore(key7, [key7, key7])", 2]); baseTransaction.sdiff([key7, key7]); - args.push(new Set()); + responseData.push(["sdiff([key7, key7])", new Set()]); baseTransaction.sdiffstore(key7, [key7]); - args.push(2); + responseData.push(["sdiffstore(key7, [key7])", 2]); baseTransaction.srem(key7, ["foo"]); - args.push(1); + responseData.push(['srem(key7, ["foo"])', 1]); + baseTransaction.sscan(key7, "0"); + responseData.push(['sscan(key7, "0")', ["0", ["bar"]]]); + baseTransaction.sscan(key7, "0", { match: "*", count: 20 }); + responseData.push([ + 'sscan(key7, "0", {match: "*", count: 20})', + ["0", ["bar"]], + ]); baseTransaction.scard(key7); - args.push(1); + responseData.push(["scard(key7)", 1]); baseTransaction.sismember(key7, "bar"); - args.push(true); + responseData.push(['sismember(key7, "bar")', true]); + + if (gte(version, "6.2.0")) { + baseTransaction.smismember(key7, ["bar", "foo", "baz"]); + responseData.push([ + 'smismember(key7, ["bar", "foo", "baz"])', + [true, false, false], + ]); + } + baseTransaction.smembers(key7); - args.push(new Set(["bar"])); + responseData.push(["smembers(key7)", new Set(["bar"])]); + baseTransaction.srandmember(key7); + responseData.push(["srandmember(key7)", "bar"]); + baseTransaction.srandmemberCount(key7, 2); + responseData.push(["srandmemberCount(key7, 2)", ["bar"]]); + baseTransaction.srandmemberCount(key7, -2); + responseData.push(["srandmemberCount(key7, -2)", ["bar", "bar"]]); baseTransaction.spop(key7); - args.push("bar"); + responseData.push(["spop(key7)", "bar"]); baseTransaction.spopCount(key7, 2); - args.push(new Set()); + responseData.push(["spopCount(key7, 2)", new Set()]); baseTransaction.smove(key7, key7, "non_existing_member"); - args.push(false); + responseData.push(['smove(key7, key7, "non_existing_member")', false]); baseTransaction.scard(key7); - args.push(0); + responseData.push(["scard(key7)", 0]); baseTransaction.zadd(key8, { member1: 1, member2: 2, @@ -427,95 +967,645 @@ export async function transactionTest( member4: 4, member5: 5, }); - args.push(5); + responseData.push(["zadd(key8, { ... } ", 5]); baseTransaction.zrank(key8, "member1"); - args.push(0); + responseData.push(['zrank(key8, "member1")', 0]); - if (!(await checkIfServerVersionLessThan("7.2.0"))) { + if (gte(version, "7.2.0")) { baseTransaction.zrankWithScore(key8, "member1"); - args.push([0, 1]); + responseData.push(['zrankWithScore(key8, "member1")', [0, 1]]); + } + + baseTransaction.zrevrank(key8, "member5"); + responseData.push(['zrevrank(key8, "member5")', 0]); + + if (gte(version, "7.2.0")) { + baseTransaction.zrevrankWithScore(key8, "member5"); + responseData.push(['zrevrankWithScore(key8, "member5")', [0, 5]]); } baseTransaction.zaddIncr(key8, "member2", 1); - args.push(3); + responseData.push(['zaddIncr(key8, "member2", 1)', 3]); + baseTransaction.zincrby(key8, 0.3, "member1"); + responseData.push(['zincrby(key8, 0.3, "member1")', 1.3]); baseTransaction.zrem(key8, ["member1"]); - args.push(1); + responseData.push(['zrem(key8, ["member1"])', 1]); baseTransaction.zcard(key8); - args.push(4); + responseData.push(["zcard(key8)", 4]); + baseTransaction.zscore(key8, "member2"); - args.push(3.0); + responseData.push(['zscore(key8, "member2")', 3.0]); baseTransaction.zrange(key8, { start: 0, stop: -1 }); - args.push(["member2", "member3", "member4", "member5"]); + responseData.push([ + "zrange(key8, { start: 0, stop: -1 })", + ["member2", "member3", "member4", "member5"], + ]); baseTransaction.zrangeWithScores(key8, { start: 0, stop: -1 }); - args.push({ member2: 3, member3: 3.5, member4: 4, member5: 5 }); + responseData.push([ + "zrangeWithScores(key8, { start: 0, stop: -1 })", + { member2: 3, member3: 3.5, member4: 4, member5: 5 }, + ]); baseTransaction.zadd(key12, { one: 1, two: 2 }); - args.push(2); - baseTransaction.zadd(key13, { one: 1, two: 2, tree: 3.5 }); - args.push(3); - baseTransaction.zinterstore(key12, [key12, key13]); - args.push(2); - baseTransaction.zcount(key8, { value: 2 }, "positiveInfinity"); - args.push(4); + responseData.push(["zadd(key12, { one: 1, two: 2 })", 2]); + baseTransaction.zscan(key12, "0"); + responseData.push(['zscan(key12, "0")', ["0", ["one", "1", "two", "2"]]]); + baseTransaction.zscan(key12, "0", { match: "*", count: 20 }); + responseData.push([ + 'zscan(key12, "0", {match: "*", count: 20})', + ["0", ["one", "1", "two", "2"]], + ]); + baseTransaction.zadd(key13, { one: 1, two: 2, three: 3.5 }); + responseData.push(["zadd(key13, { one: 1, two: 2, three: 3.5 })", 3]); + + if (gte(version, "6.2.0")) { + baseTransaction.zrangeStore(key8, key8, { start: 0, stop: -1 }); + responseData.push([ + "zrangeStore(key8, key8, { start: 0, stop: -1 })", + 4, + ]); + baseTransaction.zdiff([key13, key12]); + responseData.push(["zdiff([key13, key12])", ["three"]]); + baseTransaction.zdiffWithScores([key13, key12]); + responseData.push(["zdiffWithScores([key13, key12])", { three: 3.5 }]); + baseTransaction.zdiffstore(key13, [key13, key13]); + responseData.push(["zdiffstore(key13, [key13, key13])", 0]); + baseTransaction.zunionstore(key5, [key12, key13]); + responseData.push(["zunionstore(key5, [key12, key13])", 2]); + baseTransaction.zmscore(key12, ["two", "one"]); + responseData.push(['zmscore(key12, ["two", "one"]', [2.0, 1.0]]); + baseTransaction.zinterstore(key12, [key12, key13]); + responseData.push(["zinterstore(key12, [key12, key13])", 0]); + + if (gte(version, "6.2.0")) { + baseTransaction.zadd(key26, { one: 1, two: 2 }); + responseData.push(["zadd(key26, { one: 1, two: 2 })", 2]); + baseTransaction.zadd(key27, { one: 1, two: 2, three: 3.5 }); + responseData.push([ + "zadd(key27, { one: 1, two: 2, three: 3.5 })", + 3, + ]); + baseTransaction.zinter([key27, key26]); + responseData.push(["zinter([key27, key26])", ["one", "two"]]); + baseTransaction.zinterWithScores([key27, key26]); + responseData.push([ + "zinterWithScores([key27, key26])", + { one: 2, two: 4 }, + ]); + baseTransaction.zunionWithScores([key27, key26]); + responseData.push([ + "zunionWithScores([key27, key26])", + { one: 2, two: 4, three: 3.5 }, + ]); + } + } else { + baseTransaction.zinterstore(key12, [key12, key13]); + responseData.push(["zinterstore(key12, [key12, key13])", 2]); + } + + baseTransaction.zcount(key8, { value: 2 }, InfBoundary.PositiveInfinity); + responseData.push([ + "zcount(key8, { value: 2 }, InfBoundary.PositiveInfinity)", + 4, + ]); + baseTransaction.zlexcount( + key8, + { value: "a" }, + InfBoundary.PositiveInfinity, + ); + responseData.push([ + 'zlexcount(key8, { value: "a" }, InfBoundary.PositiveInfinity)', + 4, + ]); baseTransaction.zpopmin(key8); - args.push({ member2: 3.0 }); + responseData.push(["zpopmin(key8)", { member2: 3.0 }]); baseTransaction.zpopmax(key8); - args.push({ member5: 5 }); + responseData.push(["zpopmax(key8)", { member5: 5 }]); + baseTransaction.zadd(key8, { member6: 6 }); + responseData.push(["zadd(key8, {member6: 6})", 1]); + baseTransaction.bzpopmax([key8], 0.5); + responseData.push(["bzpopmax([key8], 0.5)", [key8, "member6", 6]]); + baseTransaction.zadd(key8, { member7: 1 }); + responseData.push(["zadd(key8, {member7: 1})", 1]); + baseTransaction.bzpopmin([key8], 0.5); + responseData.push(["bzpopmin([key8], 0.5)", [key8, "member7", 1]]); baseTransaction.zremRangeByRank(key8, 1, 1); - args.push(1); + responseData.push(["zremRangeByRank(key8, 1, 1)", 1]); baseTransaction.zremRangeByScore( key8, - "negativeInfinity", - "positiveInfinity", + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, ); - args.push(1); // key8 is now empty + responseData.push(["zremRangeByScore(key8, -Inf, +Inf)", 1]); // key8 is now empty + baseTransaction.zremRangeByLex( + key8, + InfBoundary.NegativeInfinity, + InfBoundary.PositiveInfinity, + ); + responseData.push(["zremRangeByLex(key8, -Inf, +Inf)", 0]); // key8 is already empty - if (!(await checkIfServerVersionLessThan("7.0.0"))) { + if (gte(version, "7.0.0")) { baseTransaction.zadd(key14, { one: 1.0, two: 2.0 }); - args.push(2); + responseData.push(["zadd(key14, { one: 1.0, two: 2.0 })", 2]); baseTransaction.zintercard([key8, key14]); - args.push(0); + responseData.push(["zintercard([key8, key14])", 0]); baseTransaction.zintercard([key8, key14], 1); - args.push(0); + responseData.push(["zintercard([key8, key14], 1)", 0]); + baseTransaction.zmpop([key14], ScoreFilter.MAX); + responseData.push(["zmpop([key14], MAX)", [key14, { two: 2.0 }]]); + baseTransaction.zmpop([key14], ScoreFilter.MAX, 1); + responseData.push(["zmpop([key14], MAX, 1)", [key14, { one: 1.0 }]]); + baseTransaction.zadd(key14, { one: 1.0, two: 2.0 }); + responseData.push(["zadd(key14, { one: 1.0, two: 2.0 })", 2]); + baseTransaction.bzmpop([key14], ScoreFilter.MAX, 0.1); + responseData.push([ + "bzmpop([key14], ScoreFilter.MAX, 0.1)", + [key14, { two: 2.0 }], + ]); + baseTransaction.bzmpop([key14], ScoreFilter.MAX, 0.1, 1); + responseData.push([ + "bzmpop([key14], ScoreFilter.MAX, 0.1, 1)", + [key14, { one: 1.0 }], + ]); } baseTransaction.xadd(key9, [["field", "value1"]], { id: "0-1" }); - args.push("0-1"); + responseData.push([ + 'xadd(key9, [["field", "value1"]], { id: "0-1" })', + "0-1", + ]); baseTransaction.xadd(key9, [["field", "value2"]], { id: "0-2" }); - args.push("0-2"); + responseData.push([ + 'xadd(key9, [["field", "value2"]], { id: "0-2" })', + "0-2", + ]); baseTransaction.xadd(key9, [["field", "value3"]], { id: "0-3" }); - args.push("0-3"); + responseData.push([ + 'xadd(key9, [["field", "value3"]], { id: "0-3" })', + "0-3", + ]); baseTransaction.xlen(key9); - args.push(3); + responseData.push(["xlen(key9)", 3]); + baseTransaction.xrange(key9, { value: "0-1" }, { value: "0-1" }); + responseData.push(["xrange(key9)", { "0-1": [["field", "value1"]] }]); + baseTransaction.xrevrange(key9, { value: "0-1" }, { value: "0-1" }); + responseData.push(["xrevrange(key9)", { "0-1": [["field", "value1"]] }]); baseTransaction.xread({ [key9]: "0-1" }); - args.push({ - [key9]: { - "0-2": [["field", "value2"]], - "0-3": [["field", "value3"]], + responseData.push([ + 'xread({ [key9]: "0-1" })', + { + [key9]: { + "0-2": [["field", "value2"]], + "0-3": [["field", "value3"]], + }, }, - }); + ]); baseTransaction.xtrim(key9, { method: "minid", threshold: "0-2", exact: true, }); - args.push(1); + responseData.push([ + 'xtrim(key9, { method: "minid", threshold: "0-2", exact: true }', + 1, + ]); + baseTransaction.xinfoGroups(key9); + responseData.push(["xinfoGroups(key9)", []]); + baseTransaction.xgroupCreate(key9, groupName1, "0-0"); + responseData.push(['xgroupCreate(key9, groupName1, "0-0")', "OK"]); + baseTransaction.xgroupCreate(key9, groupName2, "0-0", { mkStream: true }); + responseData.push([ + 'xgroupCreate(key9, groupName2, "0-0", { mkStream: true })', + "OK", + ]); + baseTransaction.xinfoConsumers(key9, groupName1); + responseData.push(["xinfoConsumers(key9, groupName1)", []]); + baseTransaction.xdel(key9, ["0-3", "0-5"]); + responseData.push(["xdel(key9, [['0-3', '0-5']])", 1]); + + // key9 has one entry here: {"0-2":[["field","value2"]]} + + baseTransaction.xgroupCreateConsumer(key9, groupName1, consumer); + responseData.push([ + "xgroupCreateConsumer(key9, groupName1, consumer)", + true, + ]); + baseTransaction.xreadgroup(groupName1, consumer, { [key9]: ">" }); + responseData.push([ + 'xreadgroup(groupName1, consumer, {[key9]: ">"})', + { [key9]: { "0-2": [["field", "value2"]] } }, + ]); + baseTransaction.xpending(key9, groupName1); + responseData.push([ + "xpending(key9, groupName1)", + [1, "0-2", "0-2", [[consumer, "1"]]], + ]); + baseTransaction.xpendingWithOptions(key9, groupName1, { + start: InfBoundary.NegativeInfinity, + end: InfBoundary.PositiveInfinity, + count: 10, + }); + responseData.push([ + "xpendingWithOptions(key9, groupName1, -, +, 10)", + [["0-2", consumer, 0, 1]], + ]); + baseTransaction.xclaim(key9, groupName1, consumer, 0, ["0-2"]); + responseData.push([ + 'xclaim(key9, groupName1, consumer, 0, ["0-2"])', + { "0-2": [["field", "value2"]] }, + ]); + baseTransaction.xclaim(key9, groupName1, consumer, 0, ["0-2"], { + isForce: true, + retryCount: 0, + idle: 0, + }); + responseData.push([ + 'xclaim(key9, groupName1, consumer, 0, ["0-2"], { isForce: true, retryCount: 0, idle: 0})', + { "0-2": [["field", "value2"]] }, + ]); + baseTransaction.xclaimJustId(key9, groupName1, consumer, 0, ["0-2"]); + responseData.push([ + 'xclaimJustId(key9, groupName1, consumer, 0, ["0-2"])', + ["0-2"], + ]); + baseTransaction.xclaimJustId(key9, groupName1, consumer, 0, ["0-2"], { + isForce: true, + retryCount: 0, + idle: 0, + }); + responseData.push([ + 'xclaimJustId(key9, groupName1, consumer, 0, ["0-2"], { isForce: true, retryCount: 0, idle: 0})', + ["0-2"], + ]); + + if (gte(version, "6.2.0")) { + baseTransaction.xautoclaim(key9, groupName1, consumer, 0, "0-0", 1); + responseData.push([ + 'xautoclaim(key9, groupName1, consumer, 0, "0-0", 1)', + gte(version, "7.0.0") + ? ["0-0", { "0-2": [["field", "value2"]] }, []] + : ["0-0", { "0-2": [["field", "value2"]] }], + ]); + baseTransaction.xautoclaimJustId(key9, groupName1, consumer, 0, "0-0"); + responseData.push([ + 'xautoclaimJustId(key9, groupName1, consumer, 0, "0-0")', + gte(version, "7.0.0") ? ["0-0", ["0-2"], []] : ["0-0", ["0-2"]], + ]); + } + + baseTransaction.xack(key9, groupName1, ["0-3"]); + responseData.push(["xack(key9, groupName1, ['0-3'])", 0]); + baseTransaction.xgroupSetId(key9, groupName1, "0-2"); + responseData.push(["xgroupSetId(key9, groupName1, '0-2')", "OK"]); + baseTransaction.xgroupDelConsumer(key9, groupName1, consumer); + responseData.push(["xgroupDelConsumer(key9, groupName1, consumer)", 1]); + baseTransaction.xgroupDestroy(key9, groupName1); + responseData.push(["xgroupDestroy(key9, groupName1)", true]); + baseTransaction.xgroupDestroy(key9, groupName2); + responseData.push(["xgroupDestroy(key9, groupName2)", true]); + baseTransaction.rename(key9, key10); - args.push("OK"); + responseData.push(["rename(key9, key10)", "OK"]); baseTransaction.exists([key10]); - args.push(1); + responseData.push(["exists([key10])", 1]); + baseTransaction.touch([key10]); + responseData.push(["touch([key10])", 1]); baseTransaction.renamenx(key10, key9); - args.push(true); + responseData.push(["renamenx(key10, key9)", true]); baseTransaction.exists([key9, key10]); - args.push(1); + responseData.push(["exists([key9, key10])", 1]); baseTransaction.rpush(key6, [field + "1", field + "2", field + "3"]); - args.push(3); + responseData.push([ + 'rpush(key6, [field + "1", field + "2", field + "3"])', + 3, + ]); baseTransaction.brpop([key6], 0.1); - args.push([key6, field + "3"]); + responseData.push(["brpop([key6], 0.1)", [key6, field + "3"]]); baseTransaction.blpop([key6], 0.1); - args.push([key6, field + "1"]); + responseData.push(["blpop([key6], 0.1)", [key6, field + "1"]]); + + baseTransaction.setbit(key17, 1, 1); + responseData.push(["setbit(key17, 1, 1)", 0]); + baseTransaction.getbit(key17, 1); + responseData.push(["getbit(key17, 1)", 1]); + baseTransaction.set(key17, "foobar"); + responseData.push(['set(key17, "foobar")', "OK"]); + baseTransaction.bitcount(key17); + responseData.push(["bitcount(key17)", 26]); + baseTransaction.bitcount(key17, { start: 1, end: 1 }); + responseData.push(["bitcount(key17, { start: 1, end: 1 })", 6]); + baseTransaction.bitpos(key17, 1); + responseData.push(["bitpos(key17, 1)", 1]); + + if (gte(version, "6.0.0")) { + baseTransaction.bitfieldReadOnly(key17, [ + new BitFieldGet(new SignedEncoding(5), new BitOffset(3)), + ]); + responseData.push([ + "bitfieldReadOnly(key17, [new BitFieldGet(...)])", + [6], + ]); + } + + baseTransaction.set(key19, "abcdef"); + responseData.push(['set(key19, "abcdef")', "OK"]); + baseTransaction.bitop(BitwiseOperation.AND, key19, [key19, key17]); + responseData.push([ + "bitop(BitwiseOperation.AND, key19, [key19, key17])", + 6, + ]); + baseTransaction.get(key19); + responseData.push(["get(key19)", "`bc`ab"]); + + if (gte(version, "7.0.0")) { + baseTransaction.bitcount(key17, { + start: 5, + end: 30, + indexType: BitmapIndexType.BIT, + }); + responseData.push([ + "bitcount(key17, new BitOffsetOptions(5, 30, BitmapIndexType.BIT))", + 17, + ]); + baseTransaction.bitposInterval(key17, 1, 44, 50, BitmapIndexType.BIT); + responseData.push([ + "bitposInterval(key17, 1, 44, 50, BitmapIndexType.BIT)", + 46, + ]); + } + + baseTransaction.bitfield(key17, [ + new BitFieldSet( + new UnsignedEncoding(10), + new BitOffsetMultiplier(3), + 4, + ), + ]); + responseData.push(["bitfield(key17, [new BitFieldSet(...)])", [609]]); + baseTransaction.pfadd(key11, ["a", "b", "c"]); - args.push(1); + responseData.push(['pfadd(key11, ["a", "b", "c"])', 1]); + baseTransaction.pfmerge(key11, []); + responseData.push(["pfmerge(key11, [])", "OK"]); baseTransaction.pfcount([key11]); - args.push(3); - return args; + responseData.push(["pfcount([key11])", 3]); + baseTransaction.geoadd( + key18, + new Map([ + ["Palermo", { longitude: 13.361389, latitude: 38.115556 }], + ["Catania", { longitude: 15.087269, latitude: 37.502669 }], + ]), + ); + responseData.push(["geoadd(key18, { Palermo: ..., Catania: ... })", 2]); + baseTransaction.geopos(key18, ["Palermo", "Catania"]); + responseData.push([ + 'geopos(key18, ["Palermo", "Catania"])', + [ + [13.36138933897018433, 38.11555639549629859], + [15.08726745843887329, 37.50266842333162032], + ], + ]); + baseTransaction.geodist(key18, "Palermo", "Catania"); + responseData.push(['geodist(key18, "Palermo", "Catania")', 166274.1516]); + baseTransaction.geodist(key18, "Palermo", "Catania", GeoUnit.KILOMETERS); + responseData.push([ + 'geodist(key18, "Palermo", "Catania", GeoUnit.KILOMETERS)', + 166.2742, + ]); + baseTransaction.geohash(key18, ["Palermo", "Catania", "NonExisting"]); + responseData.push([ + 'geohash(key18, ["Palermo", "Catania", "NonExisting"])', + ["sqc8b49rny0", "sqdtr74hyu0", null], + ]); + baseTransaction.zadd(key23, { one: 1.0 }); + responseData.push(["zadd(key23, {one: 1.0}", 1]); + baseTransaction.zrandmember(key23); + responseData.push(["zrandmember(key23)", "one"]); + baseTransaction.zrandmemberWithCount(key23, 1); + responseData.push(["zrandmemberWithCount(key23, 1)", ["one"]]); + baseTransaction.zrandmemberWithCountWithScores(key23, 1); + responseData.push([ + "zrandmemberWithCountWithScores(key23, 1)", + [["one", 1.0]], + ]); + + if (gte(version, "6.2.0")) { + baseTransaction + .geosearch( + key18, + { member: "Palermo" }, + { radius: 200, unit: GeoUnit.KILOMETERS }, + { sortOrder: SortOrder.ASC }, + ) + .geosearch( + key18, + { position: { longitude: 15, latitude: 37 } }, + { width: 400, height: 400, unit: GeoUnit.KILOMETERS }, + ) + .geosearch( + key18, + { member: "Palermo" }, + { radius: 200, unit: GeoUnit.KILOMETERS }, + { + sortOrder: SortOrder.ASC, + count: 2, + withCoord: true, + withDist: true, + withHash: true, + }, + ) + .geosearch( + key18, + { position: { longitude: 15, latitude: 37 } }, + { width: 400, height: 400, unit: GeoUnit.KILOMETERS }, + { + sortOrder: SortOrder.ASC, + count: 2, + withCoord: true, + withDist: true, + withHash: true, + }, + ); + responseData.push([ + 'geosearch(key18, "Palermo", R200 KM, ASC)', + ["Palermo", "Catania"], + ]); + responseData.push([ + "geosearch(key18, (15, 37), 400x400 KM, ASC)", + ["Palermo", "Catania"], + ]); + responseData.push([ + 'geosearch(key18, "Palermo", R200 KM, ASC 2 3x true)', + [ + [ + "Palermo", + [ + 0.0, + 3479099956230698, + [13.361389338970184, 38.1155563954963], + ], + ], + [ + "Catania", + [ + 166.2742, + 3479447370796909, + [15.087267458438873, 37.50266842333162], + ], + ], + ], + ]); + responseData.push([ + "geosearch(key18, (15, 37), 400x400 KM, ASC 2 3x true)", + [ + [ + "Catania", + [ + 56.4413, + 3479447370796909, + [15.087267458438873, 37.50266842333162], + ], + ], + [ + "Palermo", + [ + 190.4424, + 3479099956230698, + [13.361389338970184, 38.1155563954963], + ], + ], + ], + ]); + + baseTransaction.geosearchstore( + key25, + key18, + { position: { longitude: 15, latitude: 37 } }, + { width: 400, height: 400, unit: GeoUnit.KILOMETERS }, + ); + responseData.push([ + "geosearchstore(key25, key18, (15, 37), 400x400 KM)", + 2, + ]); + } + + const libName = "mylib1C" + uuidv4().replaceAll("-", ""); + const funcName = "myfunc1c" + uuidv4().replaceAll("-", ""); + const code = generateLuaLibCode( + libName, + new Map([[funcName, "return args[1]"]]), + true, + ); + + if (gte(version, "7.0.0")) { + baseTransaction.functionFlush(); + responseData.push(["functionFlush()", "OK"]); + baseTransaction.functionLoad(code); + responseData.push(["functionLoad(code)", libName]); + baseTransaction.functionLoad(code, true); + responseData.push(["functionLoad(code, true)", libName]); + baseTransaction.functionList({ libNamePattern: "another" }); + responseData.push(['functionList("another")', []]); + baseTransaction.fcall(funcName, [], ["one", "two"]); + responseData.push(['fcall(funcName, [], ["one", "two"])', "one"]); + baseTransaction.fcallReadonly(funcName, [], ["one", "two"]); + responseData.push([ + 'fcallReadonly(funcName, [], ["one", "two"]', + "one", + ]); + baseTransaction.functionStats(); + responseData.push([ + "functionStats()", + { + running_script: null, + engines: { LUA: { libraries_count: 1, functions_count: 1 } }, + }, + ]); + baseTransaction.functionDelete(libName); + responseData.push(["functionDelete(libName)", "OK"]); + baseTransaction.functionFlush(); + responseData.push(["functionFlush()", "OK"]); + baseTransaction.functionFlush(FlushMode.ASYNC); + responseData.push(["functionFlush(FlushMode.ASYNC)", "OK"]); + baseTransaction.functionFlush(FlushMode.SYNC); + responseData.push(["functionFlush(FlushMode.SYNC)", "OK"]); + baseTransaction.functionList({ + libNamePattern: libName, + withCode: true, + }); + responseData.push(["functionList({ libName, true})", []]); + + baseTransaction + .mset({ [key1]: "abcd", [key2]: "bcde", [key3]: "wxyz" }) + .lcs(key1, key2) + .lcs(key1, key3) + .lcsLen(key1, key2) + .lcsLen(key1, key3) + .lcsIdx(key1, key2) + .lcsIdx(key1, key2, { minMatchLen: 1 }) + .lcsIdx(key1, key2, { withMatchLen: true }) + .lcsIdx(key1, key2, { withMatchLen: true, minMatchLen: 1 }) + .del([key1, key2, key3]); + + responseData.push( + ['mset({[key1]: "abcd", [key2]: "bcde", [key3]: "wxyz"})', "OK"], + ["lcs(key1, key2)", "bcd"], + ["lcs(key1, key3)", ""], + ["lcsLen(key1, key2)", 3], + ["lcsLen(key1, key3)", 0], + [ + "lcsIdx(key1, key2)", + { + matches: [ + [ + [1, 3], + [0, 2], + ], + ], + len: 3, + }, + ], + [ + "lcsIdx(key1, key2, {minMatchLen: 1})", + { + matches: [ + [ + [1, 3], + [0, 2], + ], + ], + len: 3, + }, + ], + [ + "lcsIdx(key1, key2, {withMatchLen: true})", + { matches: [[[1, 3], [0, 2], 3]], len: 3 }, + ], + [ + "lcsIdx(key1, key2, {withMatchLen: true, minMatchLen: 1})", + { matches: [[[1, 3], [0, 2], 3]], len: 3 }, + ], + ["del([key1, key2, key3])", 3], + ); + } + + baseTransaction + .lpush(key21, ["3", "1", "2"]) + .sort(key21) + .sortStore(key21, key22) + .lrange(key22, 0, -1); + responseData.push( + ['lpush(key21, ["3", "1", "2"])', 3], + ["sort(key21)", ["1", "2", "3"]], + ["sortStore(key21, key22)", 3], + ["lrange(key22, 0, -1)", ["1", "2", "3"]], + ); + + if (gte(version, "7.0.0")) { + baseTransaction.sortReadOnly(key21); + responseData.push(["sortReadOnly(key21)", ["1", "2", "3"]]); + } + + baseTransaction.wait(1, 200); + responseData.push(["wait(1, 200)", 1]); + return responseData; } diff --git a/node/tests/tsconfig.json b/node/tests/tsconfig.json new file mode 100644 index 0000000000..f7b57f9a00 --- /dev/null +++ b/node/tests/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": "../../" + }, + "include": ["*.ts", "./*.test.ts"] +} diff --git a/python/.gitignore b/python/.gitignore index 0b5ca61d21..17dcb39f70 100644 --- a/python/.gitignore +++ b/python/.gitignore @@ -50,6 +50,7 @@ coverage.xml .hypothesis/ .pytest_cache/ cover/ +*.html # Translations *.mo diff --git a/python/DEVELOPER.md b/python/DEVELOPER.md index ac9a6555c5..2f71c6e27c 100644 --- a/python/DEVELOPER.md +++ b/python/DEVELOPER.md @@ -86,9 +86,8 @@ Before starting this step, make sure you've installed all software requirments. 1. Clone the repository: ```bash - VERSION=0.1.0 # You can modify this to other released version or set it to "main" to get the unstable branch - git clone --branch ${VERSION} https://github.com/valkey-io/valkey-glide.git - cd glide-for-redis + git clone https://github.com/valkey-io/valkey-glide.git + cd valkey-glide ``` 2. Initialize git submodule: ```bash diff --git a/python/THIRD_PARTY_LICENSES_PYTHON b/python/THIRD_PARTY_LICENSES_PYTHON index fe669c485d..e3c0129045 100644 --- a/python/THIRD_PARTY_LICENSES_PYTHON +++ b/python/THIRD_PARTY_LICENSES_PYTHON @@ -683,10 +683,23 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ahash:0.8.11 +Package: adler2:2.0.0 The following copyrights and licenses were found in the source code of this package: +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + -- + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -912,7 +925,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: allocator-api2:0.2.18 +Package: ahash:0.8.11 The following copyrights and licenses were found in the source code of this package: @@ -1141,7 +1154,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: android-tzdata:0.1.1 +Package: allocator-api2:0.2.18 The following copyrights and licenses were found in the source code of this package: @@ -1370,7 +1383,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: android_system_properties:0.1.5 +Package: android-tzdata:0.1.1 The following copyrights and licenses were found in the source code of this package: @@ -1599,7 +1612,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: arc-swap:1.7.1 +Package: android_system_properties:0.1.5 The following copyrights and licenses were found in the source code of this package: @@ -1828,7 +1841,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: arcstr:1.2.0 +Package: arc-swap:1.7.1 The following copyrights and licenses were found in the source code of this package: @@ -2055,29 +2068,9 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. - -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. - ---- -Package: async-trait:0.1.81 +Package: arcstr:1.2.0 The following copyrights and licenses were found in the source code of this package: @@ -2304,9 +2297,29 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -- + +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + ---- -Package: backoff:0.4.0 +Package: async-trait:0.1.81 The following copyrights and licenses were found in the source code of this package: @@ -2535,7 +2548,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: backtrace:0.3.73 +Package: backoff:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -2764,7 +2777,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: base64:0.22.1 +Package: backtrace:0.3.73 The following copyrights and licenses were found in the source code of this package: @@ -2993,7 +3006,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bitflags:2.6.0 +Package: base64:0.22.1 The following copyrights and licenses were found in the source code of this package: @@ -3222,7 +3235,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bumpalo:3.16.0 +Package: bitflags:2.6.0 The following copyrights and licenses were found in the source code of this package: @@ -3451,32 +3464,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bytes:1.6.1 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: cfg-if:1.0.0 +Package: bumpalo:3.16.0 The following copyrights and licenses were found in the source code of this package: @@ -3705,7 +3693,84 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: chrono:0.4.38 +Package: byteorder:1.5.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -- + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +---- + +Package: bytes:1.7.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: cfg-if:1.0.0 The following copyrights and licenses were found in the source code of this package: @@ -3934,32 +3999,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: combine:4.6.7 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: core-foundation:0.9.4 +Package: chrono:0.4.38 The following copyrights and licenses were found in the source code of this package: @@ -4188,7 +4228,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: core-foundation-sys:0.8.6 +Package: combine:4.6.7 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: core-foundation:0.9.4 The following copyrights and licenses were found in the source code of this package: @@ -4417,32 +4482,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crc16:0.4.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: crc32fast:1.4.2 +Package: core-foundation-sys:0.8.7 The following copyrights and licenses were found in the source code of this package: @@ -4671,7 +4711,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crossbeam-channel:0.5.13 +Package: crc16:0.4.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: crc32fast:1.4.2 The following copyrights and licenses were found in the source code of this package: @@ -4900,7 +4965,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crossbeam-utils:0.8.20 +Package: crossbeam-channel:0.5.13 The following copyrights and licenses were found in the source code of this package: @@ -5129,7 +5194,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: deranged:0.3.11 +Package: crossbeam-utils:0.8.20 The following copyrights and licenses were found in the source code of this package: @@ -5358,7 +5423,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: derivative:2.2.0 +Package: dashmap:6.0.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: deranged:0.3.11 The following copyrights and licenses were found in the source code of this package: @@ -5587,7 +5677,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: directories:4.0.1 +Package: derivative:2.2.0 The following copyrights and licenses were found in the source code of this package: @@ -5816,7 +5906,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: dirs-sys:0.3.7 +Package: directories:4.0.1 The following copyrights and licenses were found in the source code of this package: @@ -6045,7 +6135,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: dispose:0.5.0 +Package: dirs-sys:0.3.7 The following copyrights and licenses were found in the source code of this package: @@ -6274,7 +6364,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: dispose-derive:0.4.0 +Package: dispose:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -6503,7 +6593,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: fast-math:0.1.1 +Package: dispose-derive:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -6732,32 +6822,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: file-rotate:0.7.6 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: flate2:1.0.30 +Package: fast-math:0.1.1 The following copyrights and licenses were found in the source code of this package: @@ -6986,7 +7051,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: form_urlencoded:1.2.1 +Package: file-rotate:0.7.6 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: flate2:1.0.33 The following copyrights and licenses were found in the source code of this package: @@ -7215,7 +7305,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures:0.3.30 +Package: form_urlencoded:1.2.1 The following copyrights and licenses were found in the source code of this package: @@ -7444,7 +7534,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-channel:0.3.30 +Package: futures:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -7673,7 +7763,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-core:0.3.30 +Package: futures-channel:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -7902,7 +7992,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-executor:0.3.30 +Package: futures-core:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -8131,7 +8221,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-intrusive:0.5.0 +Package: futures-executor:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -8360,7 +8450,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-io:0.3.30 +Package: futures-intrusive:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -8589,7 +8679,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-macro:0.3.30 +Package: futures-io:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -8818,7 +8908,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-sink:0.3.30 +Package: futures-macro:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9047,7 +9137,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-task:0.3.30 +Package: futures-sink:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9276,7 +9366,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: futures-util:0.3.30 +Package: futures-task:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9505,7 +9595,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: getrandom:0.2.15 +Package: futures-util:0.3.30 The following copyrights and licenses were found in the source code of this package: @@ -9734,7 +9824,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: gimli:0.29.0 +Package: getrandom:0.2.15 The following copyrights and licenses were found in the source code of this package: @@ -9963,7 +10053,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: glide-core:0.1.0 +Package: gimli:0.29.0 The following copyrights and licenses were found in the source code of this package: @@ -10169,9 +10259,30 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. + -- + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ---- -Package: hashbrown:0.14.5 +Package: glide-core:0.1.0 The following copyrights and licenses were found in the source code of this package: @@ -10377,30 +10488,9 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- -Package: heck:0.4.1 +Package: hashbrown:0.14.5 The following copyrights and licenses were found in the source code of this package: @@ -10629,7 +10719,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: heck:0.5.0 +Package: heck:0.4.1 The following copyrights and licenses were found in the source code of this package: @@ -10858,7 +10948,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: hermit-abi:0.3.9 +Package: heck:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -11087,7 +11177,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: iana-time-zone:0.1.60 +Package: hermit-abi:0.3.9 The following copyrights and licenses were found in the source code of this package: @@ -11316,7 +11406,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: iana-time-zone-haiku:0.1.2 +Package: iana-time-zone:0.1.60 The following copyrights and licenses were found in the source code of this package: @@ -11545,7 +11635,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: idna:0.5.0 +Package: iana-time-zone-haiku:0.1.2 The following copyrights and licenses were found in the source code of this package: @@ -11774,7 +11864,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ieee754:0.2.6 +Package: idna:0.5.0 The following copyrights and licenses were found in the source code of this package: @@ -12003,7 +12093,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: indoc:2.0.5 +Package: ieee754:0.2.6 The following copyrights and licenses were found in the source code of this package: @@ -12232,63 +12322,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: instant:0.1.13 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: integer-encoding:4.0.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: itoa:1.0.11 +Package: indoc:2.0.5 The following copyrights and licenses were found in the source code of this package: @@ -12517,7 +12551,63 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: js-sys:0.3.69 +Package: instant:0.1.13 + +The following copyrights and licenses were found in the source code of this package: + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + +Package: integer-encoding:4.0.2 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: itoa:1.0.11 The following copyrights and licenses were found in the source code of this package: @@ -12746,7 +12836,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: lazy_static:1.5.0 +Package: js-sys:0.3.70 The following copyrights and licenses were found in the source code of this package: @@ -12975,7 +13065,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: libc:0.2.155 +Package: lazy_static:1.5.0 The following copyrights and licenses were found in the source code of this package: @@ -13204,32 +13294,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: libredox:0.1.3 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: lock_api:0.4.12 +Package: libc:0.2.158 The following copyrights and licenses were found in the source code of this package: @@ -13458,7 +13523,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: log:0.4.22 +Package: libredox:0.1.3 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: lock_api:0.4.12 The following copyrights and licenses were found in the source code of this package: @@ -13687,7 +13777,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: logger_core:0.1.0 +Package: log:0.4.22 The following copyrights and licenses were found in the source code of this package: @@ -13893,64 +13983,8 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. ----- - -Package: memchr:2.7.4 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to - ----- - -Package: memoffset:0.9.1 - -The following copyrights and licenses were found in the source code of this package: - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -13972,7 +14006,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: miniz_oxide:0.7.4 +Package: logger_core:0.1.0 The following copyrights and licenses were found in the source code of this package: @@ -14178,100 +14212,9 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -- - -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. - -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. - ---- -Package: mio:0.8.11 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: nanoid:0.4.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: nu-ansi-term:0.46.0 +Package: memchr:2.7.4 The following copyrights and licenses were found in the source code of this package: @@ -14294,9 +14237,61 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -- + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + ---- -Package: num-bigint:0.4.6 +Package: memoffset:0.9.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: miniz_oxide:0.7.4 The following copyrights and licenses were found in the source code of this package: @@ -14523,9 +14518,29 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -- + +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + ---- -Package: num-conv:0.1.0 +Package: miniz_oxide:0.8.0 The following copyrights and licenses were found in the source code of this package: @@ -14752,9 +14767,104 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -- + +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + ---- -Package: num-integer:0.1.46 +Package: mio:1.0.2 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: nanoid:0.4.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: nu-ansi-term:0.46.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: num-bigint:0.4.6 The following copyrights and licenses were found in the source code of this package: @@ -14983,7 +15093,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: num-traits:0.2.19 +Package: num-conv:0.1.0 The following copyrights and licenses were found in the source code of this package: @@ -15212,7 +15322,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: num_cpus:1.16.0 +Package: num-integer:0.1.46 The following copyrights and licenses were found in the source code of this package: @@ -15441,7 +15551,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: object:0.36.1 +Package: num-traits:0.2.19 The following copyrights and licenses were found in the source code of this package: @@ -15670,7 +15780,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: once_cell:1.19.0 +Package: num_cpus:1.16.0 The following copyrights and licenses were found in the source code of this package: @@ -15899,7 +16009,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: openssl-probe:0.1.5 +Package: object:0.36.3 The following copyrights and licenses were found in the source code of this package: @@ -16128,32 +16238,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: overload:0.1.1 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: parking_lot:0.12.3 +Package: once_cell:1.19.0 The following copyrights and licenses were found in the source code of this package: @@ -16382,7 +16467,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: parking_lot_core:0.9.10 +Package: openssl-probe:0.1.5 The following copyrights and licenses were found in the source code of this package: @@ -16611,7 +16696,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: percent-encoding:2.3.1 +Package: overload:0.1.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: parking_lot:0.12.3 The following copyrights and licenses were found in the source code of this package: @@ -16840,7 +16950,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project:1.1.5 +Package: parking_lot_core:0.9.10 The following copyrights and licenses were found in the source code of this package: @@ -17069,7 +17179,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-internal:1.1.5 +Package: percent-encoding:2.3.1 The following copyrights and licenses were found in the source code of this package: @@ -17298,7 +17408,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-lite:0.2.14 +Package: pin-project:1.1.5 The following copyrights and licenses were found in the source code of this package: @@ -17527,7 +17637,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-utils:0.1.0 +Package: pin-project-internal:1.1.5 The following copyrights and licenses were found in the source code of this package: @@ -17756,7 +17866,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: portable-atomic:1.6.0 +Package: pin-project-lite:0.2.14 The following copyrights and licenses were found in the source code of this package: @@ -17985,7 +18095,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: powerfmt:0.2.0 +Package: pin-utils:0.1.0 The following copyrights and licenses were found in the source code of this package: @@ -18214,7 +18324,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ppv-lite86:0.2.17 +Package: portable-atomic:1.7.0 The following copyrights and licenses were found in the source code of this package: @@ -18443,7 +18553,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro-error:1.0.4 +Package: powerfmt:0.2.0 The following copyrights and licenses were found in the source code of this package: @@ -18672,7 +18782,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro-error-attr:1.0.4 +Package: ppv-lite86:0.2.20 The following copyrights and licenses were found in the source code of this package: @@ -18901,7 +19011,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro2:1.0.86 +Package: proc-macro-error:1.0.4 The following copyrights and licenses were found in the source code of this package: @@ -19130,57 +19240,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: protobuf:3.5.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: protobuf-support:3.5.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: pyo3:0.20.3 +Package: proc-macro-error-attr:1.0.4 The following copyrights and licenses were found in the source code of this package: @@ -19409,7 +19469,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pyo3-build-config:0.20.3 +Package: proc-macro2:1.0.86 The following copyrights and licenses were found in the source code of this package: @@ -19638,7 +19698,57 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pyo3-ffi:0.20.3 +Package: protobuf:3.5.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: protobuf-support:3.5.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: pyo3:0.20.3 The following copyrights and licenses were found in the source code of this package: @@ -19867,7 +19977,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pyo3-macros:0.20.3 +Package: pyo3-build-config:0.20.3 The following copyrights and licenses were found in the source code of this package: @@ -20096,7 +20206,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pyo3-macros-backend:0.20.3 +Package: pyo3-ffi:0.20.3 The following copyrights and licenses were found in the source code of this package: @@ -20325,7 +20435,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: quote:1.0.36 +Package: pyo3-macros:0.20.3 The following copyrights and licenses were found in the source code of this package: @@ -20554,7 +20664,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand:0.8.5 +Package: pyo3-macros-backend:0.20.3 The following copyrights and licenses were found in the source code of this package: @@ -20783,7 +20893,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand_chacha:0.3.1 +Package: quote:1.0.37 The following copyrights and licenses were found in the source code of this package: @@ -21012,7 +21122,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand_core:0.6.4 +Package: rand:0.8.5 The following copyrights and licenses were found in the source code of this package: @@ -21241,88 +21351,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: redis:0.25.2 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: redox_syscall:0.5.3 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: redox_users:0.4.5 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: rustc-demangle:0.1.24 +Package: rand_chacha:0.3.1 The following copyrights and licenses were found in the source code of this package: @@ -21551,7 +21580,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls:0.22.4 +Package: rand_core:0.6.4 The following copyrights and licenses were found in the source code of this package: @@ -21759,19 +21788,86 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - -- +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: redis:0.25.2 + +The following copyrights and licenses were found in the source code of this package: + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + +Package: redox_syscall:0.5.3 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: redox_users:0.4.6 + +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -21794,7 +21890,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-native-certs:0.7.1 +Package: rustc-demangle:0.1.24 The following copyrights and licenses were found in the source code of this package: @@ -22002,20 +22098,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - - -- - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -22037,7 +22119,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pemfile:2.1.2 +Package: rustls:0.22.4 The following copyrights and licenses were found in the source code of this package: @@ -22280,7 +22362,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pki-types:1.7.0 +Package: rustls-native-certs:0.7.2 The following copyrights and licenses were found in the source code of this package: @@ -22488,6 +22570,20 @@ The following copyrights and licenses were found in the source code of this pack -- +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + -- + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -22509,25 +22605,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-webpki:0.102.5 - -The following copyrights and licenses were found in the source code of this package: - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - ----- - -Package: rustversion:1.0.17 +Package: rustls-pemfile:2.1.3 The following copyrights and licenses were found in the source code of this package: @@ -22735,6 +22813,20 @@ The following copyrights and licenses were found in the source code of this pack -- +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + -- + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -22756,7 +22848,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ryu:1.0.18 +Package: rustls-pki-types:1.8.0 The following copyrights and licenses were found in the source code of this package: @@ -22964,36 +23056,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - ----- - -Package: schannel:0.1.23 - -The following copyrights and licenses were found in the source code of this package: - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -23015,7 +23077,25 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: scopeguard:1.2.0 +Package: rustls-webpki:0.102.6 + +The following copyrights and licenses were found in the source code of this package: + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +---- + +Package: rustversion:1.0.17 The following copyrights and licenses were found in the source code of this package: @@ -23244,7 +23324,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework:2.11.1 +Package: ryu:1.0.18 The following copyrights and licenses were found in the source code of this package: @@ -23452,6 +23532,36 @@ The following copyrights and licenses were found in the source code of this pack -- +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +---- + +Package: schannel:0.1.23 + +The following copyrights and licenses were found in the source code of this package: + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -23473,7 +23583,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework-sys:2.11.1 +Package: scopeguard:1.2.0 The following copyrights and licenses were found in the source code of this package: @@ -23702,7 +23812,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde:1.0.204 +Package: security-framework:2.11.1 The following copyrights and licenses were found in the source code of this package: @@ -23931,7 +24041,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde_derive:1.0.204 +Package: security-framework-sys:2.11.1 The following copyrights and licenses were found in the source code of this package: @@ -24160,88 +24270,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: sha1_smol:1.0.0 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: sharded-slab:0.1.7 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: slab:0.4.9 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: smallvec:1.13.2 +Package: serde:1.0.209 The following copyrights and licenses were found in the source code of this package: @@ -24470,7 +24499,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: socket2:0.5.7 +Package: serde_derive:1.0.209 The following copyrights and licenses were found in the source code of this package: @@ -24699,57 +24728,38 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: spin:0.9.8 +Package: sha1_smol:1.0.1 The following copyrights and licenses were found in the source code of this package: -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: strum:0.26.3 +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: -The following copyrights and licenses were found in the source code of this package: +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -Package: strum_macros:0.26.4 +Package: sharded-slab:0.1.7 The following copyrights and licenses were found in the source code of this package: @@ -24774,38 +24784,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: subtle:2.6.1 +Package: slab:0.4.9 The following copyrights and licenses were found in the source code of this package: -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:1.0.109 +Package: smallvec:1.13.2 The following copyrights and licenses were found in the source code of this package: @@ -25034,7 +25038,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.71 +Package: socket2:0.5.7 The following copyrights and licenses were found in the source code of this package: @@ -25263,231 +25267,113 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: target-lexicon:0.12.15 +Package: spin:0.9.8 The following copyrights and licenses were found in the source code of this package: - 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. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - 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. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - 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: +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - (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 +Package: strum:0.26.3 - (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 +The following copyrights and licenses were found in the source code of this package: - (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. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - 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. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - 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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - 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. +Package: strum_macros:0.26.4 - 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. +The following copyrights and licenses were found in the source code of this package: - 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. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - END OF TERMS AND CONDITIONS +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - APPENDIX: How to apply the Apache License to your work. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. +---- - Copyright [yyyy] [name of copyright owner] +Package: subtle:2.6.1 - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +The following copyrights and licenses were found in the source code of this package: - http://www.apache.org/licenses/LICENSE-2.0 +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. ---- LLVM Exceptions to the Apache 2.0 License ---- +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -Package: thiserror:1.0.62 +Package: syn:1.0.109 The following copyrights and licenses were found in the source code of this package: @@ -25716,7 +25602,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: thiserror-impl:1.0.62 +Package: syn:2.0.76 The following copyrights and licenses were found in the source code of this package: @@ -25945,7 +25831,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: thread_local:1.1.8 +Package: target-lexicon:0.12.16 The following copyrights and licenses were found in the source code of this package: @@ -26151,30 +26037,25 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +--- LLVM Exceptions to the Apache 2.0 License ---- -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. ---- -Package: time:0.3.36 +Package: thiserror:1.0.63 The following copyrights and licenses were found in the source code of this package: @@ -26403,7 +26284,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: time-core:0.1.2 +Package: thiserror-impl:1.0.63 The following copyrights and licenses were found in the source code of this package: @@ -26632,7 +26513,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: time-macros:0.2.18 +Package: thread_local:1.1.8 The following copyrights and licenses were found in the source code of this package: @@ -26861,7 +26742,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: tinyvec:1.8.0 +Package: time:0.3.36 The following copyrights and licenses were found in the source code of this package: @@ -27088,29 +26969,9 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- - -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. - -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. - ---- -Package: tinyvec_macros:0.1.1 +Package: time-core:0.1.2 The following copyrights and licenses were found in the source code of this package: @@ -27337,31 +27198,215 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- +---- -This software is provided 'as-is', without any express or implied warranty. In no -event will the authors be held liable for any damages arising from the use of this -software. +Package: time-macros:0.2.18 -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: +The following copyrights and licenses were found in the source code of this package: -1. The origin of this software must not be misrepresented; you must not claim that - you wrote the original software. If you use this software in a product, an - acknowledgment in the product documentation would be appreciated but is not - required. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -3. This notice may not be removed or altered from any source 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. -Package: tokio:1.38.1 + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -The following copyrights and licenses were found in the source code of this package: + "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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -27384,57 +27429,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: tokio-macros:2.3.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tokio-retry:0.3.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tokio-rustls:0.25.0 +Package: tinyvec:1.8.0 The following copyrights and licenses were found in the source code of this package: @@ -27661,184 +27656,29 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----- - -Package: tokio-util:0.7.11 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing:0.1.40 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-appender:0.2.3 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-attributes:0.1.27 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-core:0.1.32 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: tracing-log:0.2.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- + -- -Package: tracing-subscriber:0.3.18 +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. -The following copyrights and licenses were found in the source code of this package: +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +3. This notice may not be removed or altered from any source distribution. ---- -Package: unicode-bidi:0.3.15 +Package: tinyvec_macros:0.1.1 The following copyrights and licenses were found in the source code of this package: @@ -28065,9 +27905,104 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + -- + +This software is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of this +software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + ---- -Package: unicode-ident:1.0.12 +Package: tokio:1.39.3 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tokio-macros:2.4.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tokio-retry:0.3.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tokio-rustls:0.25.0 The following copyrights and licenses were found in the source code of this package: @@ -28294,70 +28229,184 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -- +---- -UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE +Package: tokio-util:0.7.11 -Unicode Data Files include all data files under the directories -http://www.unicode.org/Public/, http://www.unicode.org/reports/, -http://www.unicode.org/cldr/data/, http://source.icu- -project.org/repos/icu/, and -http://www.unicode.org/utility/trac/browser/. +The following copyrights and licenses were found in the source code of this package: -Unicode Data Files do not include PDF online code charts under the -directory http://www.unicode.org/Public/. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -Software includes any source code published in the Unicode Standard or -under the directories http://www.unicode.org/Public/, -http://www.unicode.org/reports/, http://www.unicode.org/cldr/data/, -http://source.icu-project.org/repos/icu/, and -http://www.unicode.org/utility/trac/browser/. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. -NOTICE TO USER: Carefully read the following legal agreement. BY -DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA -FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY -ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF -THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, -DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -COPYRIGHT AND PERMISSION NOTICE +---- -Copyright © 1991-2016 Unicode, Inc. All rights reserved. Distributed -under the Terms of Use in http://www.unicode.org/copyright.html. +Package: tracing:0.1.40 -Permission is hereby granted, free of charge, to any person obtaining a -copy of the Unicode data files and any associated documentation (the -"Data Files") or Unicode software and any associated documentation (the -"Software") to deal in the Data Files or Software without restriction, -including without limitation the rights to use, copy, modify, merge, -publish, distribute, and/or sell copies of the Data Files or Software, -and to permit persons to whom the Data Files or Software are furnished -to do so, provided that either +The following copyrights and licenses were found in the source code of this package: -(a) this copyright and permission notice appear with all copies of the -Data Files or Software, or +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -(b) this copyright and permission notice appear in associated -Documentation. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. -THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR -ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER -RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF -CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Except as contained in this notice, the name of a copyright holder shall -not be used in advertising or otherwise to promote the sale, use or -other dealings in these Data Files or Software without prior written -authorization of the copyright holder. +---- + +Package: tracing-appender:0.2.3 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: unicode-normalization:0.1.23 +Package: tracing-attributes:0.1.27 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing-core:0.1.32 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing-log:0.2.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: tracing-subscriber:0.3.18 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: unicode-bidi:0.3.15 The following copyrights and licenses were found in the source code of this package: @@ -28586,7 +28635,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: unindent:0.2.3 +Package: unicode-ident:1.0.12 The following copyrights and licenses were found in the source code of this package: @@ -28813,27 +28862,70 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----- + -- -Package: untrusted:0.9.0 +UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE -The following copyrights and licenses were found in the source code of this package: +Unicode Data Files include all data files under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu- +project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. +Unicode Data Files do not include PDF online code charts under the +directory http://www.unicode.org/Public/. -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. +Software includes any source code published in the Unicode Standard or +under the directories http://www.unicode.org/Public/, +http://www.unicode.org/reports/, http://www.unicode.org/cldr/data/, +http://source.icu-project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA +FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY +ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF +THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, +DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2016 Unicode, Inc. All rights reserved. Distributed +under the Terms of Use in http://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of the Unicode data files and any associated documentation (the +"Data Files") or Unicode software and any associated documentation (the +"Software") to deal in the Data Files or Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, and/or sell copies of the Data Files or Software, +and to permit persons to whom the Data Files or Software are furnished +to do so, provided that either + +(a) this copyright and permission notice appear with all copies of the +Data Files or Software, or + +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR +ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or +other dealings in these Data Files or Software without prior written +authorization of the copyright holder. ---- -Package: url:2.5.0 +Package: unicode-normalization:0.1.23 The following copyrights and licenses were found in the source code of this package: @@ -29062,32 +29154,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: valuable:0.1.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: wasi:0.11.0+wasi-snapshot-preview1 +Package: unindent:0.2.3 The following copyrights and licenses were found in the source code of this package: @@ -29293,226 +29360,6 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. - -- - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ---- LLVM Exceptions to the Apache 2.0 License ---- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. - -- Permission is hereby granted, free of charge, to any person obtaining @@ -29536,7 +29383,25 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen:0.2.92 +Package: untrusted:0.9.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +---- + +Package: url:2.5.0 The following copyrights and licenses were found in the source code of this package: @@ -29765,7 +29630,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-backend:0.2.92 +Package: valuable:0.1.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: wasi:0.11.0+wasi-snapshot-preview1 The following copyrights and licenses were found in the source code of this package: @@ -29973,31 +29863,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: wasm-bindgen-macro:0.2.92 - -The following copyrights and licenses were found in the source code of this package: - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -30200,234 +30065,21 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: wasm-bindgen-macro-support:0.2.92 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +--- LLVM Exceptions to the Apache 2.0 License ---- - http://www.apache.org/licenses/LICENSE-2.0 +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. -- @@ -30452,7 +30104,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-shared:0.2.92 +Package: wasm-bindgen:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -30681,7 +30333,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: winapi:0.3.9 +Package: wasm-bindgen-backend:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -30910,7 +30562,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: winapi-i686-pc-windows-gnu:0.4.0 +Package: wasm-bindgen-macro:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -31139,7 +30791,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: winapi-x86_64-pc-windows-gnu:0.4.0 +Package: wasm-bindgen-macro-support:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -31368,7 +31020,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-core:0.52.0 +Package: wasm-bindgen-shared:0.2.93 The following copyrights and licenses were found in the source code of this package: @@ -31597,7 +31249,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-sys:0.48.0 +Package: winapi:0.3.9 The following copyrights and licenses were found in the source code of this package: @@ -31826,7 +31478,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-sys:0.52.0 +Package: winapi-i686-pc-windows-gnu:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -32055,7 +31707,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.48.5 +Package: winapi-x86_64-pc-windows-gnu:0.4.0 The following copyrights and licenses were found in the source code of this package: @@ -32284,7 +31936,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.52.6 +Package: windows-core:0.52.0 The following copyrights and licenses were found in the source code of this package: @@ -32513,7 +32165,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.48.5 +Package: windows-sys:0.52.0 The following copyrights and licenses were found in the source code of this package: @@ -32742,7 +32394,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.52.6 +Package: windows-targets:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -32971,7 +32623,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.48.5 +Package: windows_aarch64_gnullvm:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -33429,7 +33081,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.48.5 +Package: windows_i686_gnu:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -33658,7 +33310,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.52.6 +Package: windows_i686_gnullvm:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -33887,7 +33539,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnullvm:0.52.6 +Package: windows_i686_msvc:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -34116,7 +33768,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.48.5 +Package: windows_x86_64_gnu:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -34345,7 +33997,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.52.6 +Package: windows_x86_64_gnullvm:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -34574,7 +34226,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.48.5 +Package: windows_x86_64_msvc:0.52.6 The following copyrights and licenses were found in the source code of this package: @@ -34803,7 +34455,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.52.6 +Package: zerocopy:0.7.35 The following copyrights and licenses were found in the source code of this package: @@ -35011,232 +34663,26 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: windows_x86_64_gnullvm:0.48.5 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. - http://www.apache.org/licenses/LICENSE-2.0 +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -- @@ -35261,7 +34707,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnullvm:0.52.6 +Package: zerocopy-derive:0.7.35 The following copyrights and licenses were found in the source code of this package: @@ -35469,6 +34915,29 @@ The following copyrights and licenses were found in the source code of this pack -- +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + -- + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -35490,7 +34959,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_msvc:0.48.5 +Package: zeroize:1.8.1 The following copyrights and licenses were found in the source code of this package: @@ -35719,7 +35188,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_msvc:0.52.6 +Package: async-timeout:4.0.2 The following copyrights and licenses were found in the source code of this package: @@ -35925,7 +35394,11 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. - -- +---- + +Package: attrs:24.2.0 + +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -35948,237 +35421,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: zerocopy:0.7.35 +Package: black:24.8.0 The following copyrights and licenses were found in the source code of this package: - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -- - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -36200,746 +35446,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: zerocopy-derive:0.7.35 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: zeroize:1.8.1 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: async-timeout:4.0.2 - -The following copyrights and licenses were found in the source code of this package: - - 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ----- - -Package: attrs:23.2.0 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: black:24.4.2 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: cachetools:5.4.0 +Package: cachetools:5.5.0 The following copyrights and licenses were found in the source code of this package: @@ -37841,7 +36348,7 @@ The following copyrights and licenses were found in the source code of this pack ---- -Package: google-auth:2.32.0 +Package: google-auth:2.34.0 The following copyrights and licenses were found in the source code of this package: @@ -38257,7 +36764,7 @@ The following copyrights and licenses were found in the source code of this pack ---- -Package: googleapis-common-protos:1.63.2 +Package: googleapis-common-protos:1.64.0 The following copyrights and licenses were found in the source code of this package: @@ -38540,6 +37047,37 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- +Package: markupsafe:2.1.5 + +The following copyrights and licenses were found in the source code of this package: + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + Package: maturin:0.13.0 The following copyrights and licenses were found in the source code of this package: @@ -39929,7 +38467,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -Package: protobuf:5.27.2 +Package: protobuf:5.27.3 The following copyrights and licenses were found in the source code of this package: @@ -40093,7 +38631,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pyparsing:3.1.2 +Package: pyparsing:3.1.4 The following copyrights and licenses were found in the source code of this package: @@ -40351,6 +38889,411 @@ The following copyrights and licenses were found in the source code of this pack ---- +Package: pytest-html:4.1.1 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: pytest-metadata:3.1.1 + +The following copyrights and licenses were found in the source code of this package: + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + +---- + Package: requests:2.32.3 The following copyrights and licenses were found in the source code of this package: diff --git a/python/python/glide/__init__.py b/python/python/glide/__init__.py index f1f758aeed..50caeb5f4a 100644 --- a/python/python/glide/__init__.py +++ b/python/python/glide/__init__.py @@ -20,6 +20,7 @@ from glide.async_commands.command_args import Limit, ListDirection, OrderBy from glide.async_commands.core import ( ConditionalChange, + CoreCommands, ExpireOptions, ExpiryGetEx, ExpirySet, @@ -101,6 +102,8 @@ from .glide import ClusterScanCursor, Script +PubSubMsg = CoreCommands.PubSubMsg + __all__ = [ # Client "GlideClient", @@ -180,6 +183,8 @@ "TrimByMinId", "UpdateOptions", "ClusterScanCursor" + # PubSub + "PubSubMsg", # Logger "Logger", "LogLevel", diff --git a/python/python/glide/async_commands/cluster_commands.py b/python/python/glide/async_commands/cluster_commands.py index a0044c3e92..f42e25d032 100644 --- a/python/python/glide/async_commands/cluster_commands.py +++ b/python/python/glide/async_commands/cluster_commands.py @@ -18,7 +18,7 @@ TClusterResponse, TEncodable, TFunctionListResponse, - TFunctionStatsResponse, + TFunctionStatsSingleNodeResponse, TResult, TSingleNodeRoute, ) @@ -31,7 +31,7 @@ class ClusterCommands(CoreCommands): async def custom_command( self, command_args: List[TEncodable], route: Optional[Route] = None - ) -> TResult: + ) -> TClusterResponse[TResult]: """ Executes a single command, without checking inputs. See the [Valkey GLIDE Wiki](https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command) @@ -47,10 +47,11 @@ async def custom_command( case the client will route the command to the nodes defined by `route`. Defaults to None. Returns: - TResult: The returning value depends on the executed command and the route. + TClusterResponse[TResult]: The returning value depends on the executed command and the route. """ - return await self._execute_command( - RequestType.CustomCommand, command_args, route + return cast( + TClusterResponse[TResult], + await self._execute_command(RequestType.CustomCommand, command_args, route), ) async def info( @@ -492,7 +493,7 @@ async def function_kill(self, route: Optional[Route] = None) -> TOK: See https://valkey.io/commands/function-kill/ for more details. Args: - route (Optional[Route]): The command will be routed to all primary nodes, unless `route` is provided, + route (Optional[Route]): The command will be routed to all nodes, unless `route` is provided, in which case the client will route the command to the nodes defined by `route`. Returns: @@ -587,7 +588,7 @@ async def fcall_ro_route( async def function_stats( self, route: Optional[Route] = None - ) -> TClusterResponse[TFunctionStatsResponse]: + ) -> TClusterResponse[TFunctionStatsSingleNodeResponse]: """ Returns information about the function that's currently running and information about the available execution engines. @@ -595,11 +596,11 @@ async def function_stats( See https://valkey.io/commands/function-stats/ for more details Args: - route (Optional[Route]): Specifies the routing configuration for the command. The client - will route the command to the nodes defined by `route`. + route (Optional[Route]): The command will be routed automatically to all nodes, unless `route` is provided, in which + case the client will route the command to the nodes defined by `route`. Defaults to None. Returns: - TClusterResponse[TFunctionStatsResponse]: A `Mapping` with two keys: + TClusterResponse[TFunctionStatsSingleNodeResponse]: A `Mapping` with two keys: - `running_script` with information about the running script. - `engines` with information about available engines and their stats. See example for more details. @@ -623,7 +624,7 @@ async def function_stats( Since: Valkey version 7.0.0. """ return cast( - TClusterResponse[TFunctionStatsResponse], + TClusterResponse[TFunctionStatsSingleNodeResponse], await self._execute_command(RequestType.FunctionStats, [], route), ) @@ -920,6 +921,68 @@ async def publish( ) return cast(int, result) + async def pubsub_shardchannels( + self, pattern: Optional[TEncodable] = None + ) -> List[bytes]: + """ + Lists the currently active shard channels. + The command is routed to all nodes, and aggregates the response to a single array. + + See https://valkey.io/commands/pubsub-shardchannels for more details. + + Args: + pattern (Optional[TEncodable]): A glob-style pattern to match active shard channels. + If not provided, all active shard channels are returned. + + Returns: + List[bytes]: A list of currently active shard channels matching the given pattern. + If no pattern is specified, all active shard channels are returned. + + Examples: + >>> await client.pubsub_shardchannels() + [b'channel1', b'channel2'] + + >>> await client.pubsub_shardchannels("channel*") + [b'channel1', b'channel2'] + """ + command_args = [pattern] if pattern is not None else [] + return cast( + List[bytes], + await self._execute_command(RequestType.PubSubSChannels, command_args), + ) + + async def pubsub_shardnumsub( + self, channels: Optional[List[TEncodable]] = None + ) -> Mapping[bytes, int]: + """ + Returns the number of subscribers (exclusive of clients subscribed to patterns) for the specified shard channels. + + Note that it is valid to call this command without channels. In this case, it will just return an empty map. + The command is routed to all nodes, and aggregates the response to a single map of the channels and their number of subscriptions. + + See https://valkey.io/commands/pubsub-shardnumsub for more details. + + Args: + channels (Optional[List[TEncodable]]): The list of shard channels to query for the number of subscribers. + If not provided, returns an empty map. + + Returns: + Mapping[bytes, int]: A map where keys are the shard channel names and values are the number of subscribers. + + Examples: + >>> await client.pubsub_shardnumsub(["channel1", "channel2"]) + {b'channel1': 3, b'channel2': 5} + + >>> await client.pubsub_shardnumsub() + {} + """ + return cast( + Mapping[bytes, int], + await self._execute_command( + RequestType.PubSubSNumSub, channels if channels else [] + ), + ) + async def flushall( self, flush_mode: Optional[FlushMode] = None, route: Optional[Route] = None ) -> TOK: diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 3589f8d340..c3bb4e1aea 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -2849,8 +2849,7 @@ async def xread( When in cluster mode, all keys in `keys_and_ids` must map to the same hash slot. Args: - keys_and_ids (Mapping[TEncodable, TEncodable]): A mapping of keys and entry IDs to read from. The mapping is composed of a - stream's key and the ID of the entry after which the stream will be read. + keys_and_ids (Mapping[TEncodable, TEncodable]): A mapping of keys and entry IDs to read from. options (Optional[StreamReadOptions]): Options detailing how to read the stream. Returns: @@ -3053,9 +3052,8 @@ async def xreadgroup( When in cluster mode, all keys in `keys_and_ids` must map to the same hash slot. Args: - keys_and_ids (Mapping[TEncodable, TEncodable]): A mapping of stream keys to stream entry IDs to read from. The special ">" - ID returns messages that were never delivered to any other consumer. Any other valid ID will return - entries pending for the consumer with IDs greater than the one provided. + keys_and_ids (Mapping[TEncodable, TEncodable]): A mapping of stream keys to stream entry IDs to read from. + Use the special entry ID of `">"` to receive only new messages. group_name (TEncodable): The consumer group name. consumer_name (TEncodable): The consumer name. The consumer will be auto-created if it does not already exist. options (Optional[StreamReadGroupOptions]): Options detailing how to read the stream. @@ -3234,7 +3232,7 @@ async def xclaim( options (Optional[StreamClaimOptions]): Stream claim options. Returns: - A Mapping of message entries with the format + Mapping[bytes, List[List[bytes]]]: A Mapping of message entries with the format {"entryId": [["entry", "data"], ...], ...} that are claimed by the consumer. Examples: @@ -3268,7 +3266,7 @@ async def xclaim_just_id( min_idle_time_ms: int, ids: List[TEncodable], options: Optional[StreamClaimOptions] = None, - ) -> List[TEncodable]: + ) -> List[bytes]: """ Changes the ownership of a pending message. This function returns a List with only the message/entry IDs, and is equivalent to using JUSTID in the Valkey API. @@ -3284,7 +3282,7 @@ async def xclaim_just_id( options (Optional[StreamClaimOptions]): Stream claim options. Returns: - A List of message ids claimed by the consumer. + List[bytes]: A List of message ids claimed by the consumer. Examples: # read messages from streamId for consumer1 @@ -3296,7 +3294,7 @@ async def xclaim_just_id( } # "1-0" is now read, and we can assign the pending messages to consumer2 >>> await client.xclaim_just_id("mystream", "mygroup", "consumer2", 0, ["1-0"]) - ["1-0"] + [b"1-0"] """ args = [ @@ -3312,7 +3310,7 @@ async def xclaim_just_id( args.extend(options.to_args()) return cast( - List[TEncodable], + List[bytes], await self._execute_command(RequestType.XClaim, args), ) @@ -3861,7 +3859,7 @@ async def geosearch( from the sorted set or as a geospatial data (see `GeospatialData`). search_by (Union[GeoSearchByRadius, GeoSearchByBox]): The search criteria. For circular area search, see `GeoSearchByRadius`. - For rectengal area search, see `GeoSearchByBox`. + For rectangular area search, see `GeoSearchByBox`. order_by (Optional[OrderBy]): Specifies the order in which the results should be returned. - `ASC`: Sorts items from the nearest to the farthest, relative to the center point. - `DESC`: Sorts items from the farthest to the nearest, relative to the center point. @@ -4202,9 +4200,9 @@ async def zincrby( >>> await client.zincrby("my_sorted_set", 1.2, "member") 11.7 # The member existed in the set before score was altered, the new score is 11.7. >>> await client.zincrby("my_sorted_set", -1.7, "member") - 10.0 # Negetive increment, decrements the score. + 10.0 # Negative increment, decrements the score. >>> await client.zincrby("my_sorted_set", 5.5, "non_existing_member") - 5.5 # A new memeber is added to the sorted set with the score being 5.5. + 5.5 # A new member is added to the sorted set with the score being 5.5. """ return cast( float, @@ -5743,16 +5741,16 @@ async def bitfield( Returns: List[Optional[int]]: An array of results from the executed subcommands: - - `BitFieldGet` returns the value in `Offset` or `OffsetMultiplier`. - - `BitFieldSet` returns the old value in `Offset` or `OffsetMultiplier`. - - `BitFieldIncrBy` returns the new value in `Offset` or `OffsetMultiplier`. + - `BitFieldGet` returns the value in `BitOffset` or `BitOffsetMultiplier`. + - `BitFieldSet` returns the old value in `BitOffset` or `BitOffsetMultiplier`. + - `BitFieldIncrBy` returns the new value in `BitOffset` or `BitOffsetMultiplier`. - `BitFieldOverflow` determines the behavior of the "SET" and "INCRBY" subcommands when an overflow or underflow occurs. "OVERFLOW" does not return a value and does not contribute a value to the list response. Examples: >>> await client.set("my_key", "A") # "A" has binary value 01000001 - >>> await client.bitfield("my_key", [BitFieldSet(UnsignedEncoding(2), Offset(1), 3), BitFieldGet(UnsignedEncoding(2), Offset(1))]) + >>> await client.bitfield("my_key", [BitFieldSet(UnsignedEncoding(2), BitOffset(1), 3), BitFieldGet(UnsignedEncoding(2), BitOffset(1))]) [2, 3] # The old value at offset 1 with an unsigned encoding of 2 was 2. The new value at offset 1 with an unsigned encoding of 2 is 3. """ args = [key] + _create_bitfield_args(subcommands) @@ -5779,7 +5777,7 @@ async def bitfield_read_only( Examples: >>> await client.set("my_key", "A") # "A" has binary value 01000001 >>> await client.bitfield_read_only("my_key", [BitFieldGet(UnsignedEncoding(2), Offset(1))]) - [2] # The value at offset 1 with an unsigned encoding of 2 is 3. + [2] # The value at offset 1 with an unsigned encoding of 2 is 2. Since: Valkey version 6.0.0. """ @@ -6012,6 +6010,8 @@ async def restore( See https://valkey.io/commands/restore for more details. + Note: `IDLETIME` and `FREQ` modifiers cannot be set at the same time. + Args: key (TEncodable): The `key` to create. ttl (int): The expiry time (in milliseconds). If `0`, the `key` will persist. @@ -6628,3 +6628,86 @@ async def lpos( Union[int, List[int], None], await self._execute_command(RequestType.LPos, args), ) + + async def pubsub_channels( + self, pattern: Optional[TEncodable] = None + ) -> List[bytes]: + """ + Lists the currently active channels. + The command is routed to all nodes, and aggregates the response to a single array. + + See https://valkey.io/commands/pubsub-channels for more details. + + Args: + pattern (Optional[TEncodable]): A glob-style pattern to match active channels. + If not provided, all active channels are returned. + + Returns: + List[bytes]: A list of currently active channels matching the given pattern. + If no pattern is specified, all active channels are returned. + + Examples: + >>> await client.pubsub_channels() + [b"channel1", b"channel2"] + + >>> await client.pubsub_channels("news.*") + [b"news.sports", "news.weather"] + """ + + return cast( + List[bytes], + await self._execute_command( + RequestType.PubSubChannels, [pattern] if pattern else [] + ), + ) + + async def pubsub_numpat(self) -> int: + """ + Returns the number of unique patterns that are subscribed to by clients. + + Note: This is the total number of unique patterns all the clients are subscribed to, + not the count of clients subscribed to patterns. + The command is routed to all nodes, and aggregates the response the sum of all pattern subscriptions. + + See https://valkey.io/commands/pubsub-numpat for more details. + + Returns: + int: The number of unique patterns. + + Examples: + >>> await client.pubsub_numpat() + 3 + """ + return cast(int, await self._execute_command(RequestType.PubSubNumPat, [])) + + async def pubsub_numsub( + self, channels: Optional[List[TEncodable]] = None + ) -> Mapping[bytes, int]: + """ + Returns the number of subscribers (exclusive of clients subscribed to patterns) for the specified channels. + + Note that it is valid to call this command without channels. In this case, it will just return an empty map. + The command is routed to all nodes, and aggregates the response to a single map of the channels and their number of subscriptions. + + See https://valkey.io/commands/pubsub-numsub for more details. + + Args: + channels (Optional[List[TEncodable]]): The list of channels to query for the number of subscribers. + If not provided, returns an empty map. + + Returns: + Mapping[bytes, int]: A map where keys are the channel names and values are the number of subscribers. + + Examples: + >>> await client.pubsub_numsub(["channel1", "channel2"]) + {b'channel1': 3, b'channel2': 5} + + >>> await client.pubsub_numsub() + {} + """ + return cast( + Mapping[bytes, int], + await self._execute_command( + RequestType.PubSubNumSub, channels if channels else [] + ), + ) diff --git a/python/python/glide/async_commands/standalone_commands.py b/python/python/glide/async_commands/standalone_commands.py index 37815e14c4..1659134984 100644 --- a/python/python/glide/async_commands/standalone_commands.py +++ b/python/python/glide/async_commands/standalone_commands.py @@ -18,7 +18,7 @@ TOK, TEncodable, TFunctionListResponse, - TFunctionStatsResponse, + TFunctionStatsFullResponse, TResult, ) from glide.protobuf.command_request_pb2 import RequestType @@ -374,6 +374,8 @@ async def function_kill(self) -> TOK: Kills a function that is currently executing. This command only terminates read-only functions. + FUNCTION KILL runs on all nodes of the server, including primary and replicas. + See https://valkey.io/commands/function-kill/ for more details. Returns: @@ -390,39 +392,51 @@ async def function_kill(self) -> TOK: await self._execute_command(RequestType.FunctionKill, []), ) - async def function_stats(self) -> TFunctionStatsResponse: + async def function_stats(self) -> TFunctionStatsFullResponse: """ Returns information about the function that's currently running and information about the available execution engines. + FUNCTION STATS runs on all nodes of the server, including primary and replicas. + The response includes a mapping from node address to the command response for that node. + See https://valkey.io/commands/function-stats/ for more details Returns: - TFunctionStatsResponse: A `Mapping` with two keys: + TFunctionStatsFullResponse: A Map where the key is the node address and the value is a Map of two keys: - `running_script` with information about the running script. - `engines` with information about available engines and their stats. See example for more details. Examples: >>> await client.function_stats() - { - 'running_script': { - 'name': 'foo', - 'command': ['FCALL', 'foo', '0', 'hello'], - 'duration_ms': 7758 + {b"addr": { # Response from the master node + b'running_script': { + b'name': b'foo', + b'command': [b'FCALL', b'foo', b'0', b'hello'], + b'duration_ms': 7758 }, - 'engines': { - 'LUA': { - 'libraries_count': 1, - 'functions_count': 1, + b'engines': { + b'LUA': { + b'libraries_count': 1, + b'functions_count': 1, + } + } + }, + b"addr2": { # Response from a replica + b'running_script': None, + b"engines": { + b'LUA': { + b'libraries_count': 1, + b'functions_count': 1, } } - } + }} Since: Valkey version 7.0.0. """ return cast( - TFunctionStatsResponse, + TFunctionStatsFullResponse, await self._execute_command(RequestType.FunctionStats, []), ) diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 6167b549c1..8aa0f0fa1d 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -2028,7 +2028,7 @@ def function_stats(self: TTransaction) -> TTransaction: See https://valkey.io/commands/function-stats/ for more details Command Response: - TFunctionStatsResponse: A `Mapping` with two keys: + TFunctionStatsSingleNodeResponse: A `Mapping` with two keys: - `running_script` with information about the running script. - `engines` with information about available engines and their stats. See example for more details. @@ -2108,6 +2108,8 @@ def restore( See https://valkey.io/commands/restore for more details. + Note: `IDLETIME` and `FREQ` modifiers cannot be set at the same time. + Args: key (TEncodable): The `key` to create. ttl (int): The expiry time (in milliseconds). If `0`, the `key` will persist. @@ -2297,8 +2299,7 @@ def xread( See https://valkey.io/commands/xread for more details. Args: - keys_and_ids (Mapping[TEncodable, TEncodable]): A mapping of keys and entry IDs to read from. The mapping is composed of a - stream's key and the ID of the entry after which the stream will be read. + keys_and_ids (Mapping[TEncodable, TEncodable]): A mapping of stream keys to stream entry IDs to read from. options (Optional[StreamReadOptions]): Options detailing how to read the stream. Command response: @@ -2448,9 +2449,8 @@ def xreadgroup( See https://valkey.io/commands/xreadgroup for more details. Args: - keys_and_ids (Mapping[TEncodable, TEncodable]): A mapping of stream keys to stream entry IDs to read from. The special ">" - ID returns messages that were never delivered to any other consumer. Any other valid ID will return - entries pending for the consumer with IDs greater than the one provided. + keys_and_ids (Mapping[TEncodable, TEncodable]): A mapping of stream keys to stream entry IDs to read from. + Use the special entry ID of `">"` to receive only new messages. group_name (TEncodable): The consumer group name. consumer_name (TEncodable): The consumer name. The consumer will be auto-created if it does not already exist. options (Optional[StreamReadGroupOptions]): Options detailing how to read the stream. @@ -4196,9 +4196,9 @@ def bitfield( Command response: List[Optional[int]]: An array of results from the executed subcommands: - - `BitFieldGet` returns the value in `Offset` or `OffsetMultiplier`. - - `BitFieldSet` returns the old value in `Offset` or `OffsetMultiplier`. - - `BitFieldIncrBy` returns the new value in `Offset` or `OffsetMultiplier`. + - `BitFieldGet` returns the value in `BitOffset` or `BitOffsetMultiplier`. + - `BitFieldSet` returns the old value in `BitOffset` or `BitOffsetMultiplier`. + - `BitFieldIncrBy` returns the new value in `BitOffset` or `BitOffsetMultiplier`. - `BitFieldOverflow` determines the behavior of the "SET" and "INCRBY" subcommands when an overflow or underflow occurs. "OVERFLOW" does not return a value and does not contribute a value to the list response. @@ -4780,6 +4780,62 @@ def xclaim_just_id( return self.append_command(RequestType.XClaim, args) + def pubsub_channels( + self: TTransaction, pattern: Optional[TEncodable] = None + ) -> TTransaction: + """ + Lists the currently active channels. + + See https://valkey.io/commands/pubsub-channels for details. + + Args: + pattern (Optional[TEncodable]): A glob-style pattern to match active channels. + If not provided, all active channels are returned. + + Command response: + List[bytes]: A list of currently active channels matching the given pattern. + If no pattern is specified, all active channels are returned. + """ + + return self.append_command( + RequestType.PubSubChannels, [pattern] if pattern else [] + ) + + def pubsub_numpat(self: TTransaction) -> TTransaction: + """ + Returns the number of unique patterns that are subscribed to by clients. + + Note: This is the total number of unique patterns all the clients are subscribed to, + not the count of clients subscribed to patterns. + + See https://valkey.io/commands/pubsub-numpat for details. + + Command response: + int: The number of unique patterns. + """ + return self.append_command(RequestType.PubSubNumPat, []) + + def pubsub_numsub( + self: TTransaction, channels: Optional[List[TEncodable]] = None + ) -> TTransaction: + """ + Returns the number of subscribers (exclusive of clients subscribed to patterns) for the specified channels. + + Note that it is valid to call this command without channels. In this case, it will just return an empty map. + + See https://valkey.io/commands/pubsub-numsub for details. + + Args: + channels (Optional[List[str]]): The list of channels to query for the number of subscribers. + If not provided, returns an empty map. + + Command response: + Mapping[bytes, int]: A map where keys are the channel names and values are the number of subscribers. + """ + return self.append_command( + RequestType.PubSubNumSub, channels if channels else [] + ) + class Transaction(BaseTransaction): """ @@ -5172,4 +5228,44 @@ def publish( RequestType.SPublish if sharded else RequestType.Publish, [channel, message] ) + def pubsub_shardchannels( + self, pattern: Optional[TEncodable] = None + ) -> "ClusterTransaction": + """ + Lists the currently active shard channels. + + See https://valkey.io/commands/pubsub-shardchannels for details. + + Args: + pattern (Optional[TEncodable]): A glob-style pattern to match active shard channels. + If not provided, all active shard channels are returned. + + Command response: + List[bytes]: A list of currently active shard channels matching the given pattern. + If no pattern is specified, all active shard channels are returned. + """ + command_args = [pattern] if pattern is not None else [] + return self.append_command(RequestType.PubSubSChannels, command_args) + + def pubsub_shardnumsub( + self, channels: Optional[List[TEncodable]] = None + ) -> "ClusterTransaction": + """ + Returns the number of subscribers (exclusive of clients subscribed to patterns) for the specified shard channels. + + Note that it is valid to call this command without channels. In this case, it will just return an empty map. + + See https://valkey.io/commands/pubsub-shardnumsub for details. + + Args: + channels (Optional[List[str]]): The list of shard channels to query for the number of subscribers. + If not provided, returns an empty map. + + Command response: + Mapping[bytes, int]: A map where keys are the shard channel names and values are the number of subscribers. + """ + return self.append_command( + RequestType.PubSubSNumSub, channels if channels else [] + ) + # TODO: add all CLUSTER commands diff --git a/python/python/glide/constants.py b/python/python/glide/constants.py index 24c30d8de7..754aacf6fa 100644 --- a/python/python/glide/constants.py +++ b/python/python/glide/constants.py @@ -42,15 +42,25 @@ Union[bytes, List[Mapping[bytes, Union[bytes, Set[bytes]]]]], ] ] -TFunctionStatsResponse = Mapping[ +# Response for function stats command on a single node. +# The response holds a map with 2 keys: Current running function / script and information about it, and the engines and the information about it. +TFunctionStatsSingleNodeResponse = Mapping[ bytes, Union[ None, Mapping[ - bytes, Union[Mapping[bytes, Mapping[bytes, int]], bytes, int, List[bytes]] + bytes, + Union[Mapping[bytes, Mapping[bytes, int]], bytes, int, List[bytes]], ], ], ] +# Full response for function stats command across multiple nodes. +# It maps node address to the per-node response. +TFunctionStatsFullResponse = Mapping[ + bytes, + TFunctionStatsSingleNodeResponse, +] + TXInfoStreamResponse = Mapping[ bytes, Union[bytes, int, Mapping[bytes, Optional[List[List[bytes]]]]] diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index d46e490b70..a5026e70dc 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -76,7 +76,7 @@ ProtocolVersion, ServerCredentials, ) -from glide.constants import OK, TEncodable, TFunctionStatsResponse, TResult +from glide.constants import OK, TEncodable, TFunctionStatsSingleNodeResponse, TResult from glide.exceptions import TimeoutError as GlideTimeoutError from glide.glide_client import GlideClient, GlideClusterClient, TGlideClient from glide.routes import ( @@ -412,9 +412,6 @@ async def test_info_server_replication(self, glide_client: TGlideClient): info_res = get_first_result(await glide_client.info([InfoSection.SERVER])) info = info_res.decode() assert "# Server" in info - cluster_mode = parse_info_response(info_res)["redis_mode"] - expected_cluster_mode = isinstance(glide_client, GlideClusterClient) - assert cluster_mode == "cluster" if expected_cluster_mode else "standalone" info = get_first_result( await glide_client.info([InfoSection.REPLICATION]) ).decode() @@ -8206,14 +8203,14 @@ async def test_function_delete_with_routing( await glide_client.function_delete(lib_name) assert "Library not found" in str(e) - @pytest.mark.parametrize("cluster_mode", [False]) + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) - async def test_function_stats(self, glide_client: GlideClient): + async def test_function_stats(self, glide_client: TGlideClient): min_version = "7.0.0" if await check_if_server_version_lt(glide_client, min_version): return pytest.mark.skip(reason=f"Redis version required >= {min_version}") - lib_name = "functionStats" + lib_name = "functionStats_without_route" func_name = lib_name assert await glide_client.function_flush(FlushMode.SYNC) == OK @@ -8222,7 +8219,10 @@ async def test_function_stats(self, glide_client: GlideClient): assert await glide_client.function_load(code, True) == lib_name.encode() response = await glide_client.function_stats() - check_function_stats_response(response, [], 1, 1) + for node_response in response.values(): + check_function_stats_response( + cast(TFunctionStatsSingleNodeResponse, node_response), [], 1, 1 + ) code = generate_lua_lib_code( lib_name + "_2", @@ -8234,56 +8234,74 @@ async def test_function_stats(self, glide_client: GlideClient): ) response = await glide_client.function_stats() - check_function_stats_response(response, [], 2, 3) + for node_response in response.values(): + check_function_stats_response( + cast(TFunctionStatsSingleNodeResponse, node_response), [], 2, 3 + ) assert await glide_client.function_flush(FlushMode.SYNC) == OK response = await glide_client.function_stats() - check_function_stats_response(response, [], 0, 0) + for node_response in response.values(): + check_function_stats_response( + cast(TFunctionStatsSingleNodeResponse, node_response), [], 0, 0 + ) - @pytest.mark.parametrize("cluster_mode", [True]) + @pytest.mark.parametrize("cluster_mode", [False, True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) - async def test_function_stats_cluster(self, glide_client: GlideClusterClient): + async def test_function_stats_running_script( + self, request, cluster_mode, protocol, glide_client: TGlideClient + ): min_version = "7.0.0" if await check_if_server_version_lt(glide_client, min_version): return pytest.mark.skip(reason=f"Redis version required >= {min_version}") - lib_name = "functionStats_without_route" - func_name = lib_name - assert await glide_client.function_flush(FlushMode.SYNC) == OK - - # function $funcName returns first argument - code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, False) - assert await glide_client.function_load(code, True) == lib_name.encode() + lib_name = f"mylib1C{get_random_string(5)}" + func_name = f"myfunc1c{get_random_string(5)}" + code = create_lua_lib_with_long_running_function(lib_name, func_name, 10, True) - response = await glide_client.function_stats() - for node_response in response.values(): - check_function_stats_response( - cast(TFunctionStatsResponse, node_response), [], 1, 1 - ) + # load the library + assert await glide_client.function_load(code, replace=True) == lib_name.encode() - code = generate_lua_lib_code( - lib_name + "_2", - {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, - False, + # create a second client to run fcall + test_client = await create_client( + request, cluster_mode=cluster_mode, protocol=protocol, timeout=30000 ) - assert ( - await glide_client.function_load(code, True) == (lib_name + "_2").encode() + + test_client2 = await create_client( + request, cluster_mode=cluster_mode, protocol=protocol, timeout=30000 ) - response = await glide_client.function_stats() - for node_response in response.values(): - check_function_stats_response( - cast(TFunctionStatsResponse, node_response), [], 2, 3 - ) + async def endless_fcall_route_call(): + await test_client.fcall_ro(func_name, arguments=[]) - assert await glide_client.function_flush(FlushMode.SYNC) == OK + async def wait_and_function_stats(): + # it can take a few seconds for FCALL to register as running + await asyncio.sleep(3) + result = await test_client2.function_stats() + running_scripts = False + for res in result.values(): + if res.get(b"running_script"): + if running_scripts: + raise Exception("Already running script on a different node") + running_scripts = True + assert res.get(b"running_script").get(b"name") == func_name.encode() + assert res.get(b"running_script").get(b"command") == [ + b"FCALL_RO", + func_name.encode(), + b"0", + ] + assert res.get(b"running_script").get(b"duration_ms") > 0 - response = await glide_client.function_stats() - for node_response in response.values(): - check_function_stats_response( - cast(TFunctionStatsResponse, node_response), [], 0, 0 - ) + assert running_scripts + + await asyncio.gather( + endless_fcall_route_call(), + wait_and_function_stats(), + ) + + await test_client.close() + await test_client2.close() @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) @@ -8311,12 +8329,12 @@ async def test_function_stats_with_routing( response = await glide_client.function_stats(route) if single_route: check_function_stats_response( - cast(TFunctionStatsResponse, response), [], 1, 1 + cast(TFunctionStatsSingleNodeResponse, response), [], 1, 1 ) else: for node_response in response.values(): check_function_stats_response( - cast(TFunctionStatsResponse, node_response), [], 1, 1 + cast(TFunctionStatsSingleNodeResponse, node_response), [], 1, 1 ) code = generate_lua_lib_code( @@ -8332,12 +8350,12 @@ async def test_function_stats_with_routing( response = await glide_client.function_stats(route) if single_route: check_function_stats_response( - cast(TFunctionStatsResponse, response), [], 2, 3 + cast(TFunctionStatsSingleNodeResponse, response), [], 2, 3 ) else: for node_response in response.values(): check_function_stats_response( - cast(TFunctionStatsResponse, node_response), [], 2, 3 + cast(TFunctionStatsSingleNodeResponse, node_response), [], 2, 3 ) assert await glide_client.function_flush(FlushMode.SYNC, route) == OK @@ -8345,12 +8363,12 @@ async def test_function_stats_with_routing( response = await glide_client.function_stats(route) if single_route: check_function_stats_response( - cast(TFunctionStatsResponse, response), [], 0, 0 + cast(TFunctionStatsSingleNodeResponse, response), [], 0, 0 ) else: for node_response in response.values(): check_function_stats_response( - cast(TFunctionStatsResponse, node_response), [], 0, 0 + cast(TFunctionStatsSingleNodeResponse, node_response), [], 0, 0 ) @pytest.mark.parametrize("cluster_mode", [True, False]) @@ -8379,20 +8397,10 @@ async def test_function_kill_no_write( request, cluster_mode=cluster_mode, protocol=protocol, timeout=15000 ) - # call fcall to run the function - # make sure that fcall routes to a primary node, and not a replica - # if this happens, function_kill and function_stats won't find the function and will fail - primaryRoute = SlotKeyRoute(SlotType.PRIMARY, lib_name) - async def endless_fcall_route_call(): # fcall is supposed to be killed, and will return a RequestError with pytest.raises(RequestError) as e: - if cluster_mode: - await test_client.fcall_ro_route( - func_name, arguments=[], route=primaryRoute - ) - else: - await test_client.fcall_ro(func_name, arguments=[]) + await test_client.fcall_ro(func_name, arguments=[]) assert "Script killed by user" in str(e) async def wait_and_function_kill(): @@ -9823,9 +9831,13 @@ async def test_sscan(self, glide_client: GlideClusterClient): assert result[result_collection_index] == [] # Negative cursor - result = await glide_client.sscan(key1, "-1") - assert result[result_cursor_index] == initial_cursor.encode() - assert result[result_collection_index] == [] + if await check_if_server_version_lt(glide_client, "7.9.0"): + result = await glide_client.sscan(key1, "-1") + assert result[result_cursor_index] == initial_cursor.encode() + assert result[result_collection_index] == [] + else: + with pytest.raises(RequestError): + await glide_client.sscan(key2, "-1") # Result contains the whole set assert await glide_client.sadd(key1, char_members) == len(char_members) @@ -9933,9 +9945,13 @@ async def test_zscan(self, glide_client: GlideClusterClient): assert result[result_collection_index] == [] # Negative cursor - result = await glide_client.zscan(key1, "-1") - assert result[result_cursor_index] == initial_cursor.encode() - assert result[result_collection_index] == [] + if await check_if_server_version_lt(glide_client, "7.9.0"): + result = await glide_client.zscan(key1, "-1") + assert result[result_cursor_index] == initial_cursor.encode() + assert result[result_collection_index] == [] + else: + with pytest.raises(RequestError): + await glide_client.zscan(key2, "-1") # Result contains the whole set assert await glide_client.zadd(key1, char_map) == len(char_map) @@ -10046,9 +10062,13 @@ async def test_hscan(self, glide_client: GlideClusterClient): assert result[result_collection_index] == [] # Negative cursor - result = await glide_client.hscan(key1, "-1") - assert result[result_cursor_index] == initial_cursor.encode() - assert result[result_collection_index] == [] + if await check_if_server_version_lt(glide_client, "7.9.0"): + result = await glide_client.hscan(key1, "-1") + assert result[result_cursor_index] == initial_cursor.encode() + assert result[result_collection_index] == [] + else: + with pytest.raises(RequestError): + await glide_client.hscan(key2, "-1") # Result contains the whole set assert await glide_client.hset(key1, char_map) == len(char_map) diff --git a/python/python/tests/test_pubsub.py b/python/python/tests/test_pubsub.py index 23b5bb6709..fd30c4ff70 100644 --- a/python/python/tests/test_pubsub.py +++ b/python/python/tests/test_pubsub.py @@ -13,7 +13,7 @@ GlideClusterClientConfiguration, ProtocolVersion, ) -from glide.constants import OK +from glide.constants import OK, TEncodable from glide.exceptions import ConfigurationError from glide.glide_client import BaseClient, GlideClient, GlideClusterClient, TGlideClient from tests.conftest import create_client @@ -2257,3 +2257,498 @@ async def test_pubsub_context_with_no_callback_raise_error( with pytest.raises(ConfigurationError): await create_two_clients_with_pubsub(request, cluster_mode, pub_sub_exact) + + @pytest.mark.parametrize("cluster_mode", [True, False]) + async def test_pubsub_channels(self, request, cluster_mode: bool): + """ + Tests the pubsub_channels command functionality. + + This test verifies that the pubsub_channels command correctly returns + the active channels matching a specified pattern. + """ + client1, client2, client = None, None, None + try: + channel1 = "test_channel1" + channel2 = "test_channel2" + channel3 = "some_channel3" + pattern = "test_*" + + client = await create_client(request, cluster_mode) + # Assert no channels exists yet + assert await client.pubsub_channels() == [] + + pub_sub = create_pubsub_subscription( + cluster_mode, + { + GlideClusterClientConfiguration.PubSubChannelModes.Exact: { + channel1, + channel2, + channel3, + } + }, + { + GlideClientConfiguration.PubSubChannelModes.Exact: { + channel1, + channel2, + channel3, + } + }, + ) + + channel1_bytes = channel1.encode() + channel2_bytes = channel2.encode() + channel3_bytes = channel3.encode() + + client1, client2 = await create_two_clients_with_pubsub( + request, cluster_mode, pub_sub + ) + + # Test pubsub_channels without pattern + channels = await client2.pubsub_channels() + assert set(channels) == {channel1_bytes, channel2_bytes, channel3_bytes} + + # Test pubsub_channels with pattern + channels_with_pattern = await client2.pubsub_channels(pattern) + assert set(channels_with_pattern) == {channel1_bytes, channel2_bytes} + + # Test with non-matching pattern + non_matching_channels = await client2.pubsub_channels("non_matching_*") + assert len(non_matching_channels) == 0 + + finally: + await client_cleanup(client1, pub_sub if cluster_mode else None) + await client_cleanup(client2, None) + await client_cleanup(client, None) + + @pytest.mark.parametrize("cluster_mode", [True, False]) + async def test_pubsub_numpat(self, request, cluster_mode: bool): + """ + Tests the pubsub_numpat command functionality. + + This test verifies that the pubsub_numpat command correctly returns + the number of unique patterns that are subscribed to by clients. + """ + client1, client2, client = None, None, None + try: + pattern1 = "test_*" + pattern2 = "another_*" + + # Create a client and check initial number of patterns + client = await create_client(request, cluster_mode) + assert await client.pubsub_numpat() == 0 + + # Set up subscriptions with patterns + pub_sub = create_pubsub_subscription( + cluster_mode, + { + GlideClusterClientConfiguration.PubSubChannelModes.Pattern: { + pattern1, + pattern2, + } + }, + { + GlideClientConfiguration.PubSubChannelModes.Pattern: { + pattern1, + pattern2, + } + }, + ) + + client1, client2 = await create_two_clients_with_pubsub( + request, cluster_mode, pub_sub + ) + + # Test pubsub_numpat + num_patterns = await client2.pubsub_numpat() + assert num_patterns == 2 + + finally: + await client_cleanup(client1, pub_sub if cluster_mode else None) + await client_cleanup(client2, None) + await client_cleanup(client, None) + + @pytest.mark.parametrize("cluster_mode", [True, False]) + async def test_pubsub_numsub(self, request, cluster_mode: bool): + """ + Tests the pubsub_numsub command functionality. + + This test verifies that the pubsub_numsub command correctly returns + the number of subscribers for specified channels. + """ + client1, client2, client3, client4, client = None, None, None, None, None + try: + channel1 = "test_channel1" + channel2 = "test_channel2" + channel3 = "test_channel3" + channel4 = "test_channel4" + + # Set up subscriptions + pub_sub1 = create_pubsub_subscription( + cluster_mode, + { + GlideClusterClientConfiguration.PubSubChannelModes.Exact: { + channel1, + channel2, + channel3, + } + }, + { + GlideClientConfiguration.PubSubChannelModes.Exact: { + channel1, + channel2, + channel3, + } + }, + ) + pub_sub2 = create_pubsub_subscription( + cluster_mode, + { + GlideClusterClientConfiguration.PubSubChannelModes.Exact: { + channel2, + channel3, + } + }, + { + GlideClientConfiguration.PubSubChannelModes.Exact: { + channel2, + channel3, + } + }, + ) + pub_sub3 = create_pubsub_subscription( + cluster_mode, + {GlideClusterClientConfiguration.PubSubChannelModes.Exact: {channel3}}, + {GlideClientConfiguration.PubSubChannelModes.Exact: {channel3}}, + ) + + channel1_bytes = channel1.encode() + channel2_bytes = channel2.encode() + channel3_bytes = channel3.encode() + channel4_bytes = channel4.encode() + + # Create a client and check initial subscribers + client = await create_client(request, cluster_mode) + assert await client.pubsub_numsub([channel1, channel2, channel3]) == { + channel1_bytes: 0, + channel2_bytes: 0, + channel3_bytes: 0, + } + + client1, client2 = await create_two_clients_with_pubsub( + request, cluster_mode, pub_sub1, pub_sub2 + ) + client3, client4 = await create_two_clients_with_pubsub( + request, cluster_mode, pub_sub3 + ) + + # Test pubsub_numsub + subscribers = await client2.pubsub_numsub( + [channel1_bytes, channel2_bytes, channel3_bytes, channel4_bytes] + ) + assert subscribers == { + channel1_bytes: 1, + channel2_bytes: 2, + channel3_bytes: 3, + channel4_bytes: 0, + } + + # Test pubsub_numsub with no channels + empty_subscribers = await client2.pubsub_numsub() + assert empty_subscribers == {} + + finally: + await client_cleanup(client1, pub_sub1 if cluster_mode else None) + await client_cleanup(client2, pub_sub2 if cluster_mode else None) + await client_cleanup(client3, pub_sub3 if cluster_mode else None) + await client_cleanup(client4, None) + await client_cleanup(client, None) + + @pytest.mark.parametrize("cluster_mode", [True]) + async def test_pubsub_shardchannels(self, request, cluster_mode: bool): + """ + Tests the pubsub_shardchannels command functionality. + + This test verifies that the pubsub_shardchannels command correctly returns + the active sharded channels matching a specified pattern. + """ + client1, client2, client = None, None, None + try: + channel1 = "test_shardchannel1" + channel2 = "test_shardchannel2" + channel3 = "some_shardchannel3" + pattern = "test_*" + + client = await create_client(request, cluster_mode) + min_version = "7.0.0" + if await check_if_server_version_lt(client, min_version): + pytest.skip(reason=f"Valkey version required >= {min_version}") + assert type(client) == GlideClusterClient + # Assert no sharded channels exist yet + assert await client.pubsub_shardchannels() == [] + + pub_sub = create_pubsub_subscription( + cluster_mode, + { + GlideClusterClientConfiguration.PubSubChannelModes.Sharded: { + channel1, + channel2, + channel3, + } + }, + {}, # Empty dict for non-cluster mode as sharded channels are not supported + ) + + channel1_bytes = channel1.encode() + channel2_bytes = channel2.encode() + channel3_bytes = channel3.encode() + + client1, client2 = await create_two_clients_with_pubsub( + request, cluster_mode, pub_sub + ) + + min_version = "7.0.0" + if await check_if_server_version_lt(client1, min_version): + pytest.skip(reason=f"Valkey version required >= {min_version}") + + assert type(client2) == GlideClusterClient + + # Test pubsub_shardchannels without pattern + channels = await client2.pubsub_shardchannels() + assert set(channels) == {channel1_bytes, channel2_bytes, channel3_bytes} + + # Test pubsub_shardchannels with pattern + channels_with_pattern = await client2.pubsub_shardchannels(pattern) + assert set(channels_with_pattern) == {channel1_bytes, channel2_bytes} + + # Test with non-matching pattern + assert await client2.pubsub_shardchannels("non_matching_*") == [] + + finally: + await client_cleanup(client1, pub_sub if cluster_mode else None) + await client_cleanup(client2, None) + await client_cleanup(client, None) + + @pytest.mark.parametrize("cluster_mode", [True]) + async def test_pubsub_shardnumsub(self, request, cluster_mode: bool): + """ + Tests the pubsub_shardnumsub command functionality. + + This test verifies that the pubsub_shardnumsub command correctly returns + the number of subscribers for specified sharded channels. + """ + client1, client2, client3, client4, client = None, None, None, None, None + try: + channel1 = "test_shardchannel1" + channel2 = "test_shardchannel2" + channel3 = "test_shardchannel3" + channel4 = "test_shardchannel4" + + # Set up subscriptions + pub_sub1 = create_pubsub_subscription( + cluster_mode, + { + GlideClusterClientConfiguration.PubSubChannelModes.Sharded: { + channel1, + channel2, + channel3, + } + }, + {}, + ) + pub_sub2 = create_pubsub_subscription( + cluster_mode, + { + GlideClusterClientConfiguration.PubSubChannelModes.Sharded: { + channel2, + channel3, + } + }, + {}, + ) + pub_sub3 = create_pubsub_subscription( + cluster_mode, + { + GlideClusterClientConfiguration.PubSubChannelModes.Sharded: { + channel3 + } + }, + {}, + ) + + channel1_bytes = channel1.encode() + channel2_bytes = channel2.encode() + channel3_bytes = channel3.encode() + channel4_bytes = channel4.encode() + + # Create a client and check initial subscribers + client = await create_client(request, cluster_mode) + min_version = "7.0.0" + if await check_if_server_version_lt(client, min_version): + pytest.skip(reason=f"Valkey version required >= {min_version}") + assert type(client) == GlideClusterClient + assert await client.pubsub_shardnumsub([channel1, channel2, channel3]) == { + channel1_bytes: 0, + channel2_bytes: 0, + channel3_bytes: 0, + } + + client1, client2 = await create_two_clients_with_pubsub( + request, cluster_mode, pub_sub1, pub_sub2 + ) + + min_version = "7.0.0" + if await check_if_server_version_lt(client1, min_version): + pytest.skip(reason=f"Valkey version required >= {min_version}") + + client3, client4 = await create_two_clients_with_pubsub( + request, cluster_mode, pub_sub3 + ) + + assert type(client4) == GlideClusterClient + + # Test pubsub_shardnumsub + subscribers = await client4.pubsub_shardnumsub( + [channel1, channel2, channel3, channel4] + ) + assert subscribers == { + channel1_bytes: 1, + channel2_bytes: 2, + channel3_bytes: 3, + channel4_bytes: 0, + } + + # Test pubsub_shardnumsub with no channels + empty_subscribers = await client4.pubsub_shardnumsub() + assert empty_subscribers == {} + + finally: + await client_cleanup(client1, pub_sub1 if cluster_mode else None) + await client_cleanup(client2, pub_sub2 if cluster_mode else None) + await client_cleanup(client3, pub_sub3 if cluster_mode else None) + await client_cleanup(client4, None) + await client_cleanup(client, None) + + @pytest.mark.parametrize("cluster_mode", [True]) + async def test_pubsub_channels_and_shardchannels_separation( + self, request, cluster_mode: bool + ): + """ + Tests that pubsub_channels doesn't return sharded channels and pubsub_shardchannels + doesn't return regular channels. + """ + client1, client2 = None, None + try: + regular_channel = "regular_channel" + shard_channel = "shard_channel" + + pub_sub = create_pubsub_subscription( + cluster_mode, + { + GlideClusterClientConfiguration.PubSubChannelModes.Exact: { + regular_channel + }, + GlideClusterClientConfiguration.PubSubChannelModes.Sharded: { + shard_channel + }, + }, + {GlideClientConfiguration.PubSubChannelModes.Exact: {regular_channel}}, + ) + + regular_channel_bytes, shard_channel_bytes = ( + regular_channel.encode(), + shard_channel.encode(), + ) + + client1, client2 = await create_two_clients_with_pubsub( + request, cluster_mode, pub_sub + ) + + min_version = "7.0.0" + if await check_if_server_version_lt(client1, min_version): + pytest.skip(reason=f"Valkey version required >= {min_version}") + + assert type(client2) == GlideClusterClient + # Test pubsub_channels + assert await client2.pubsub_channels() == [regular_channel_bytes] + + # Test pubsub_shardchannels + assert await client2.pubsub_shardchannels() == [shard_channel_bytes] + + finally: + await client_cleanup(client1, pub_sub if cluster_mode else None) + await client_cleanup(client2, None) + + @pytest.mark.parametrize("cluster_mode", [True]) + async def test_pubsub_numsub_and_shardnumsub_separation( + self, request, cluster_mode: bool + ): + """ + Tests that pubsub_numsub doesn't count sharded channel subscribers and pubsub_shardnumsub + doesn't count regular channel subscribers. + """ + client1, client2 = None, None + try: + regular_channel = "regular_channel" + shard_channel = "shard_channel" + + pub_sub1 = create_pubsub_subscription( + cluster_mode, + { + GlideClusterClientConfiguration.PubSubChannelModes.Exact: { + regular_channel + }, + GlideClusterClientConfiguration.PubSubChannelModes.Sharded: { + shard_channel + }, + }, + {}, + ) + pub_sub2 = create_pubsub_subscription( + cluster_mode, + { + GlideClusterClientConfiguration.PubSubChannelModes.Exact: { + regular_channel + }, + GlideClusterClientConfiguration.PubSubChannelModes.Sharded: { + shard_channel + }, + }, + {}, + ) + + regular_channel_bytes: bytes = regular_channel.encode() + shard_channel_bytes: bytes = shard_channel.encode() + + client1, client2 = await create_two_clients_with_pubsub( + request, cluster_mode, pub_sub1, pub_sub2 + ) + + min_version = "7.0.0" + if await check_if_server_version_lt(client1, min_version): + pytest.skip(reason=f"Valkey version required >= {min_version}") + + assert type(client2) == GlideClusterClient + + # Test pubsub_numsub + regular_subscribers = await client2.pubsub_numsub( + [regular_channel_bytes, shard_channel_bytes] + ) + + assert regular_subscribers == { + regular_channel_bytes: 2, + shard_channel_bytes: 0, + } + + # Test pubsub_shardnumsub + shard_subscribers = await client2.pubsub_shardnumsub( + [regular_channel_bytes, shard_channel_bytes] + ) + + assert shard_subscribers == { + regular_channel_bytes: 0, + shard_channel_bytes: 2, + } + + finally: + await client_cleanup(client1, pub_sub1 if cluster_mode else None) + await client_cleanup(client2, pub_sub2 if cluster_mode else None) diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 2b1293b943..a279b76f87 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -772,6 +772,13 @@ async def transaction_test( transaction.lcs_idx(key23, key24, with_match_len=True) args.append({b"matches": [[[4, 7], [5, 8], 4], [[1, 3], [0, 2], 3]], b"len": 7}) + transaction.pubsub_channels(pattern="*") + args.append([]) + transaction.pubsub_numpat() + args.append(0) + transaction.pubsub_numsub() + args.append({}) + return args @@ -835,9 +842,8 @@ async def test_transaction_custom_unsupported_command( transaction.custom_command(["WATCH", key]) with pytest.raises(RequestError) as e: await self.exec_transaction(glide_client, transaction) - assert "WATCH inside MULTI is not allowed" in str( - e - ) # TODO : add an assert on EXEC ABORT + + assert "not allowed" in str(e) # TODO : add an assert on EXEC ABORT @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) @@ -882,6 +888,13 @@ async def test_cluster_transaction(self, glide_client: GlideClusterClient): else: transaction.publish("test_message", keyslot, True) expected = await transaction_test(transaction, keyslot, glide_client) + + if not await check_if_server_version_lt(glide_client, "7.0.0"): + transaction.pubsub_shardchannels() + expected.append([]) + transaction.pubsub_shardnumsub() + expected.append({}) + result = await glide_client.exec(transaction) assert isinstance(result, list) assert isinstance(result[0], bytes) diff --git a/python/python/tests/tests_server_modules/test_json.py b/python/python/tests/tests_server_modules/test_json.py index c6ee1a72f4..2c9ddabce4 100644 --- a/python/python/tests/tests_server_modules/test_json.py +++ b/python/python/tests/tests_server_modules/test_json.py @@ -15,12 +15,6 @@ @pytest.mark.asyncio class TestJson: - @pytest.mark.parametrize("cluster_mode", [True, False]) - @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) - async def test_json_module_is_loaded(self, glide_client: TGlideClient): - res = parse_info_response(await glide_client.info([InfoSection.MODULES])) - assert "ReJSON" in res["module"] - @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_json_set_get(self, glide_client: TGlideClient): @@ -30,15 +24,15 @@ async def test_json_set_get(self, glide_client: TGlideClient): assert await json.set(glide_client, key, "$", OuterJson.dumps(json_value)) == OK result = await json.get(glide_client, key, ".") - assert isinstance(result, str) + assert isinstance(result, bytes) assert OuterJson.loads(result) == json_value result = await json.get(glide_client, key, ["$.a", "$.b"]) - assert isinstance(result, str) + assert isinstance(result, bytes) assert OuterJson.loads(result) == {"$.a": [1.0], "$.b": [2]} assert await json.get(glide_client, "non_existing_key", "$") is None - assert await json.get(glide_client, key, "$.d") == "[]" + assert await json.get(glide_client, key, "$.d") == b"[]" @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) @@ -56,16 +50,16 @@ async def test_json_set_get_multiple_values(self, glide_client: TGlideClient): ) result = await json.get(glide_client, key, "$..c") - assert isinstance(result, str) + assert isinstance(result, bytes) assert OuterJson.loads(result) == [True, 1, 2] result = await json.get(glide_client, key, ["$..c", "$.c"]) - assert isinstance(result, str) + assert isinstance(result, bytes) assert OuterJson.loads(result) == {"$..c": [True, 1, 2], "$.c": [True]} assert await json.set(glide_client, key, "$..c", '"new_value"') == OK result = await json.get(glide_client, key, "$..c") - assert isinstance(result, str) + assert isinstance(result, bytes) assert OuterJson.loads(result) == ["new_value"] * 3 @pytest.mark.parametrize("cluster_mode", [True, False]) @@ -105,7 +99,7 @@ async def test_json_set_conditional_set(self, glide_client: TGlideClient): is None ) - assert await json.get(glide_client, key, ".a") == "1.0" + assert await json.get(glide_client, key, ".a") == b"1.0" assert ( await json.set( @@ -118,7 +112,7 @@ async def test_json_set_conditional_set(self, glide_client: TGlideClient): == OK ) - assert await json.get(glide_client, key, ".a") == "4.5" + assert await json.get(glide_client, key, ".a") == b"4.5" @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) @@ -138,16 +132,14 @@ async def test_json_get_formatting(self, glide_client: TGlideClient): glide_client, key, "$", JsonGetOptions(indent=" ", newline="\n", space=" ") ) - expected_result = '[\n {\n "a": 1.0,\n "b": 2,\n "c": {\n "d": 3,\n "e": 4\n }\n }\n]' + expected_result = b'[\n {\n "a": 1.0,\n "b": 2,\n "c": {\n "d": 3,\n "e": 4\n }\n }\n]' assert result == expected_result result = await json.get( glide_client, key, "$", JsonGetOptions(indent="~", newline="\n", space="*") ) - expected_result = ( - '[\n~{\n~~"a":*1.0,\n~~"b":*2,\n~~"c":*{\n~~~"d":*3,\n~~~"e":*4\n~~}\n~}\n]' - ) + expected_result = b'[\n~{\n~~"a":*1.0,\n~~"b":*2,\n~~"c":*{\n~~~"d":*3,\n~~~"e":*4\n~~}\n~}\n]' assert result == expected_result @pytest.mark.parametrize("cluster_mode", [True, False]) @@ -159,10 +151,10 @@ async def test_del(self, glide_client: TGlideClient): assert await json.set(glide_client, key, "$", OuterJson.dumps(json_value)) == OK assert await json.delete(glide_client, key, "$..a") == 2 - assert await json.get(glide_client, key, "$..a") == "[]" + assert await json.get(glide_client, key, "$..a") == b"[]" result = await json.get(glide_client, key, "$") - assert isinstance(result, str) + assert isinstance(result, bytes) assert OuterJson.loads(result) == [{"b": {"b": 2.5, "c": True}}] assert await json.delete(glide_client, key, "$") == 1 @@ -178,10 +170,10 @@ async def test_forget(self, glide_client: TGlideClient): assert await json.set(glide_client, key, "$", OuterJson.dumps(json_value)) == OK assert await json.forget(glide_client, key, "$..a") == 2 - assert await json.get(glide_client, key, "$..a") == "[]" + assert await json.get(glide_client, key, "$..a") == b"[]" result = await json.get(glide_client, key, "$") - assert isinstance(result, str) + assert isinstance(result, bytes) assert OuterJson.loads(result) == [{"b": {"b": 2.5, "c": True}}] assert await json.forget(glide_client, key, "$") == 1 diff --git a/python/python/tests/utils/utils.py b/python/python/tests/utils/utils.py index 96f08a7b5a..ff156974f5 100644 --- a/python/python/tests/utils/utils.py +++ b/python/python/tests/utils/utils.py @@ -8,7 +8,7 @@ from glide.constants import ( TClusterResponse, TFunctionListResponse, - TFunctionStatsResponse, + TFunctionStatsSingleNodeResponse, TResult, ) from glide.glide_client import TGlideClient @@ -77,11 +77,11 @@ def get_random_string(length): async def check_if_server_version_lt(client: TGlideClient, min_version: str) -> bool: - # TODO: change it to pytest fixture after we'll implement a sync client - info_str = await client.info([InfoSection.SERVER]) - server_version = parse_info_response(info_str).get("redis_version") - assert server_version is not None - return version.parse(server_version) < version.parse(min_version) + # TODO: change to pytest fixture after sync client is implemented + info = parse_info_response(await client.info([InfoSection.SERVER])) + version_str = info.get("valkey_version") or info.get("redis_version") + assert version_str is not None, "Server version not found in INFO response" + return version.parse(version_str) < version.parse(min_version) def compare_maps( @@ -309,7 +309,7 @@ def check_function_list_response( def check_function_stats_response( - response: TFunctionStatsResponse, + response: TFunctionStatsSingleNodeResponse, running_function: List[bytes], lib_count: int, function_count: int, @@ -318,7 +318,7 @@ def check_function_stats_response( Validate whether `FUNCTION STATS` response contains required info. Args: - response (TFunctionStatsResponse): The response from server. + response (TFunctionStatsSingleNodeResponse): The response from server. running_function (List[bytes]): Command line of running function expected. Empty, if nothing expected. lib_count (int): Expected libraries count. function_count (int): Expected functions count. diff --git a/python/requirements.txt b/python/requirements.txt index b114a23fe4..12eb7d5e2b 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -5,3 +5,4 @@ protobuf==3.20.* pytest==7.1.2 pytest-asyncio==0.19.0 typing_extensions==4.8.0 +pytest-html diff --git a/submodules/redis-rs b/submodules/redis-rs index ee3119d17d..b43a07e7f7 160000 --- a/submodules/redis-rs +++ b/submodules/redis-rs @@ -1 +1 @@ -Subproject commit ee3119d17dfafc57aba154be991efaccbe2833f9 +Subproject commit b43a07e7f76e3f341661b95b40df0d60ba6f89f8 diff --git a/utils/TestUtils.ts b/utils/TestUtils.ts index 9df26d9d98..0a6f581088 100644 --- a/utils/TestUtils.ts +++ b/utils/TestUtils.ts @@ -1,5 +1,11 @@ +/** + * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + */ + +import { exec, execFile } from "child_process"; +import { lt } from "semver"; + const PY_SCRIPT_PATH = __dirname + "/cluster_manager.py"; -import { execFile } from "child_process"; function parseOutput(input: string): { clusterFolder: string; @@ -32,10 +38,39 @@ function parseOutput(input: string): { export class RedisCluster { private addresses: [string, number][]; private clusterFolder: string | undefined; + private version: string; - private constructor(addresses: [string, number][], clusterFolder?: string) { + private constructor( + version: string, + addresses: [string, number][], + clusterFolder?: string + ) { this.addresses = addresses; this.clusterFolder = clusterFolder; + this.version = version; + } + + private static async detectVersion(): Promise { + return new Promise((resolve, reject) => { + const extractVersion = (stdout: string): string => + stdout.split("v=")[1].split(" ")[0]; + + // First, try with `valkey-server -v` + exec("valkey-server -v", (error, stdout) => { + if (error) { + // If `valkey-server` fails, try `redis-server -v` + exec("redis-server -v", (error, stdout) => { + if (error) { + reject(error); + } else { + resolve(extractVersion(stdout)); + } + }); + } else { + resolve(extractVersion(stdout)); + } + }); + }); } public static createCluster( @@ -74,17 +109,25 @@ export class RedisCluster { } else { const { clusterFolder, addresses: ports } = parseOutput(stdout); - resolve(new RedisCluster(ports, clusterFolder)); + + resolve( + RedisCluster.detectVersion().then( + (ver) => + new RedisCluster(ver, ports, clusterFolder) + ) + ); } } ); }); } - public static initFromExistingCluster( + public static async initFromExistingCluster( addresses: [string, number][] - ): RedisCluster { - return new RedisCluster(addresses, ""); + ): Promise { + return RedisCluster.detectVersion().then( + (ver) => new RedisCluster(ver, addresses, "") + ); } public ports(): number[] { @@ -95,6 +138,14 @@ export class RedisCluster { return this.addresses; } + public getVersion(): string { + return this.version; + } + + public checkIfServerVersionLessThan(minVersion: string): boolean { + return lt(this.version, minVersion); + } + public async close() { if (this.clusterFolder) { await new Promise((resolve, reject) => { diff --git a/utils/cluster_manager.py b/utils/cluster_manager.py index 8eedcb0e4d..03adcaba00 100644 --- a/utils/cluster_manager.py +++ b/utils/cluster_manager.py @@ -280,11 +280,31 @@ def start_redis_server( ) -> Tuple[RedisServer, str]: port = port if port else next_free_port() logging.debug(f"Creating server {host}:{port}") + # Create sub-folder for each node node_folder = f"{cluster_folder}/{port}" Path(node_folder).mkdir(exist_ok=True) + + # Determine which server to use by checking `valkey-server` and `redis-server` + def get_server_command() -> str: + for server in ["valkey-server", "redis-server"]: + try: + result = subprocess.run( + ["which", server], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + if result.returncode == 0: + return server + except Exception as e: + logging.error(f"Error checking {server}: {e}") + raise Exception("Neither valkey-server nor redis-server found in the system.") + + server_name = get_server_command() + # Define command arguments cmd_args = [ - "redis-server", + server_name, f"{'--tls-port' if tls else '--port'}", str(port), "--cluster-enabled", @@ -315,6 +335,7 @@ def start_redis_server( raise Exception( f"Failed to execute command: {str(p.args)}\n Return code: {p.returncode}\n Error: {err}" ) + server = RedisServer(host, port) return server, node_folder diff --git a/utils/get_licenses_from_ort.py b/utils/get_licenses_from_ort.py index c0216343fa..3d9853ea53 100644 --- a/utils/get_licenses_from_ort.py +++ b/utils/get_licenses_from_ort.py @@ -44,7 +44,7 @@ def __init__(self, name: str, ort_results_folder: str) -> None: """ Args: name (str): the language name. - ort_results_folder (str): The relative path to the ort results folder from the root of the glide-for-redis directory. + ort_results_folder (str): The relative path to the ort results folder from the root of the valkey-glide directory. """ folder_path = f"{SCRIPT_PATH}/../{ort_results_folder}" self.analyzer_result_file = f"{folder_path}/analyzer-result.json" diff --git a/utils/package.json b/utils/package.json index 1d7c771a8a..0bbd5c9d5b 100644 --- a/utils/package.json +++ b/utils/package.json @@ -13,10 +13,12 @@ "license": "Apache-2.0", "devDependencies": { "@types/node": "^20.12.12", + "@types/semver": "^7.5.8", "prettier": "^2.8.8" }, "dependencies": { "child_process": "^1.0.2", + "semver": "^7.6.3", "typescript": "^5.4.5" } }