From e799dffee59e8cf1b410c980405b4e0af7e22266 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Fri, 3 Feb 2023 00:36:52 +0100 Subject: [PATCH 1/3] feat: intial implementation - Creates a helia directory on startup, the location is dependant on your operating system - Helia CLI - `helia daemon` starts a node with an RPC-over-libp2p server - `helia status` tells you if the daemon is running or not - `helia info` prints out node information online or offline - `helia rpc users` lists users allowed to use the rpc server (defaults to the current user) - `helia rpc useradd` allows a new user to use the rpc server - `helia rpc rmuser` removes a user from the rpc server - Unixfs CLI - `unixfs add` adds a file or folder of files - `unixfs cat` cats a file - `unixfs stat` prints unixfs stats about a dag --- .github/dependabot.yml | 11 + .github/workflows/automerge.yml | 8 + .github/workflows/js-test-and-release.yml | 143 ++ .github/workflows/main.yml | 152 ++ .github/workflows/stale.yml | 26 + .gitignore | 8 + LICENSE | 4 + LICENSE-APACHE | 5 + LICENSE-MIT | 19 + package.json | 50 + packages/cli-utils/.aegir.js | 6 + packages/cli-utils/LICENSE | 4 + packages/cli-utils/LICENSE-APACHE | 5 + packages/cli-utils/LICENSE-MIT | 19 + packages/cli-utils/README.md | 44 + packages/cli-utils/package.json | 199 ++ packages/cli-utils/src/create-helia.ts | 89 + packages/cli-utils/src/find-helia-dir.ts | 16 + packages/cli-utils/src/find-helia.ts | 148 ++ packages/cli-utils/src/format.ts | 75 + packages/cli-utils/src/generate-auth.ts | 21 + packages/cli-utils/src/index.ts | 304 +++ packages/cli-utils/src/load-rpc-keychain.ts | 38 + packages/cli-utils/src/print-help.ts | 47 + packages/cli-utils/test/index.spec.ts | 7 + packages/cli-utils/tsconfig.json | 15 + packages/helia-cli/.aegir.js | 6 + packages/helia-cli/LICENSE | 4 + packages/helia-cli/LICENSE-APACHE | 5 + packages/helia-cli/LICENSE-MIT | 19 + packages/helia-cli/README.md | 44 + packages/helia-cli/package.json | 158 ++ packages/helia-cli/src/commands/daemon.ts | 96 + packages/helia-cli/src/commands/id.ts | 16 + packages/helia-cli/src/commands/index.ts | 12 + packages/helia-cli/src/commands/init.ts | 253 ++ packages/helia-cli/src/commands/rpc/index.ts | 18 + packages/helia-cli/src/commands/rpc/rmuser.ts | 25 + .../helia-cli/src/commands/rpc/useradd.ts | 41 + packages/helia-cli/src/commands/rpc/users.ts | 18 + packages/helia-cli/src/commands/status.ts | 40 + packages/helia-cli/src/index.ts | 18 + packages/helia-cli/test/index.spec.ts | 7 + packages/helia-cli/tsconfig.json | 18 + packages/rpc-client/.aegir.js | 6 + packages/rpc-client/LICENSE | 4 + packages/rpc-client/LICENSE-APACHE | 5 + packages/rpc-client/LICENSE-MIT | 19 + packages/rpc-client/README.md | 53 + packages/rpc-client/package.json | 158 ++ .../src/commands/authorization/get.ts | 19 + .../src/commands/blockstore/batch.ts | 84 + .../src/commands/blockstore/close.ts | 11 + .../src/commands/blockstore/delete-many.ts | 22 + .../src/commands/blockstore/delete.ts | 18 + .../src/commands/blockstore/get-many.ts | 22 + .../rpc-client/src/commands/blockstore/get.ts | 22 + .../rpc-client/src/commands/blockstore/has.ts | 22 + .../src/commands/blockstore/open.ts | 11 + .../src/commands/blockstore/put-many.ts | 23 + .../rpc-client/src/commands/blockstore/put.ts | 19 + .../src/commands/blockstore/query-keys.ts | 16 + .../src/commands/blockstore/query.ts | 19 + packages/rpc-client/src/commands/info.ts | 27 + .../rpc-client/src/commands/utils/rpc-call.ts | 112 + packages/rpc-client/src/index.ts | 69 + packages/rpc-client/test/index.spec.ts | 9 + packages/rpc-client/tsconfig.json | 15 + packages/rpc-protocol/LICENSE | 4 + packages/rpc-protocol/LICENSE-APACHE | 5 + packages/rpc-protocol/LICENSE-MIT | 19 + packages/rpc-protocol/README.md | 44 + packages/rpc-protocol/package.json | 177 ++ packages/rpc-protocol/src/authorization.proto | 13 + packages/rpc-protocol/src/authorization.ts | 171 ++ packages/rpc-protocol/src/blockstore.proto | 170 ++ packages/rpc-protocol/src/blockstore.ts | 2106 +++++++++++++++++ packages/rpc-protocol/src/datastore.proto | 164 ++ packages/rpc-protocol/src/datastore.ts | 2085 ++++++++++++++++ packages/rpc-protocol/src/index.ts | 31 + packages/rpc-protocol/src/root.proto | 13 + packages/rpc-protocol/src/root.ts | 167 ++ packages/rpc-protocol/src/rpc.proto | 32 + packages/rpc-protocol/src/rpc.ts | 409 ++++ packages/rpc-protocol/tsconfig.json | 11 + packages/rpc-server/.aegir.js | 6 + packages/rpc-server/LICENSE | 4 + packages/rpc-server/LICENSE-APACHE | 5 + packages/rpc-server/LICENSE-MIT | 19 + packages/rpc-server/README.md | 53 + packages/rpc-server/package.json | 159 ++ .../src/handlers/authorization/get.ts | 103 + .../src/handlers/blockstore/batch.ts | 40 + .../src/handlers/blockstore/close.ts | 9 + .../src/handlers/blockstore/delete-many.ts | 32 + .../src/handlers/blockstore/delete.ts | 26 + .../src/handlers/blockstore/get-many.ts | 32 + .../rpc-server/src/handlers/blockstore/get.ts | 27 + .../rpc-server/src/handlers/blockstore/has.ts | 27 + .../src/handlers/blockstore/open.ts | 9 + .../src/handlers/blockstore/put-many.ts | 32 + .../rpc-server/src/handlers/blockstore/put.ts | 26 + .../src/handlers/blockstore/query-keys.ts | 25 + .../src/handlers/blockstore/query.ts | 26 + packages/rpc-server/src/handlers/index.ts | 36 + packages/rpc-server/src/handlers/info.ts | 27 + packages/rpc-server/src/index.ts | 144 ++ .../rpc-server/src/utils/multiaddr-to-url.ts | 22 + packages/rpc-server/test/index.spec.ts | 7 + packages/rpc-server/tsconfig.json | 15 + packages/unixfs-cli/.aegir.js | 6 + packages/unixfs-cli/LICENSE | 4 + packages/unixfs-cli/LICENSE-APACHE | 5 + packages/unixfs-cli/LICENSE-MIT | 19 + packages/unixfs-cli/README.md | 44 + packages/unixfs-cli/package.json | 156 ++ packages/unixfs-cli/src/commands/add.ts | 108 + packages/unixfs-cli/src/commands/cat.ts | 51 + packages/unixfs-cli/src/commands/index.ts | 10 + packages/unixfs-cli/src/commands/stat.ts | 55 + packages/unixfs-cli/src/index.ts | 18 + .../unixfs-cli/src/utils/date-to-mtime.ts | 11 + packages/unixfs-cli/src/utils/glob-source.ts | 95 + packages/unixfs-cli/test/index.spec.ts | 7 + packages/unixfs-cli/tsconfig.json | 15 + 125 files changed, 10182 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/automerge.yml create mode 100644 .github/workflows/js-test-and-release.yml create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/stale.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 package.json create mode 100644 packages/cli-utils/.aegir.js create mode 100644 packages/cli-utils/LICENSE create mode 100644 packages/cli-utils/LICENSE-APACHE create mode 100644 packages/cli-utils/LICENSE-MIT create mode 100644 packages/cli-utils/README.md create mode 100644 packages/cli-utils/package.json create mode 100644 packages/cli-utils/src/create-helia.ts create mode 100644 packages/cli-utils/src/find-helia-dir.ts create mode 100644 packages/cli-utils/src/find-helia.ts create mode 100644 packages/cli-utils/src/format.ts create mode 100644 packages/cli-utils/src/generate-auth.ts create mode 100644 packages/cli-utils/src/index.ts create mode 100644 packages/cli-utils/src/load-rpc-keychain.ts create mode 100644 packages/cli-utils/src/print-help.ts create mode 100644 packages/cli-utils/test/index.spec.ts create mode 100644 packages/cli-utils/tsconfig.json create mode 100644 packages/helia-cli/.aegir.js create mode 100644 packages/helia-cli/LICENSE create mode 100644 packages/helia-cli/LICENSE-APACHE create mode 100644 packages/helia-cli/LICENSE-MIT create mode 100644 packages/helia-cli/README.md create mode 100644 packages/helia-cli/package.json create mode 100644 packages/helia-cli/src/commands/daemon.ts create mode 100644 packages/helia-cli/src/commands/id.ts create mode 100644 packages/helia-cli/src/commands/index.ts create mode 100644 packages/helia-cli/src/commands/init.ts create mode 100644 packages/helia-cli/src/commands/rpc/index.ts create mode 100644 packages/helia-cli/src/commands/rpc/rmuser.ts create mode 100644 packages/helia-cli/src/commands/rpc/useradd.ts create mode 100644 packages/helia-cli/src/commands/rpc/users.ts create mode 100644 packages/helia-cli/src/commands/status.ts create mode 100644 packages/helia-cli/src/index.ts create mode 100644 packages/helia-cli/test/index.spec.ts create mode 100644 packages/helia-cli/tsconfig.json create mode 100644 packages/rpc-client/.aegir.js create mode 100644 packages/rpc-client/LICENSE create mode 100644 packages/rpc-client/LICENSE-APACHE create mode 100644 packages/rpc-client/LICENSE-MIT create mode 100644 packages/rpc-client/README.md create mode 100644 packages/rpc-client/package.json create mode 100644 packages/rpc-client/src/commands/authorization/get.ts create mode 100644 packages/rpc-client/src/commands/blockstore/batch.ts create mode 100644 packages/rpc-client/src/commands/blockstore/close.ts create mode 100644 packages/rpc-client/src/commands/blockstore/delete-many.ts create mode 100644 packages/rpc-client/src/commands/blockstore/delete.ts create mode 100644 packages/rpc-client/src/commands/blockstore/get-many.ts create mode 100644 packages/rpc-client/src/commands/blockstore/get.ts create mode 100644 packages/rpc-client/src/commands/blockstore/has.ts create mode 100644 packages/rpc-client/src/commands/blockstore/open.ts create mode 100644 packages/rpc-client/src/commands/blockstore/put-many.ts create mode 100644 packages/rpc-client/src/commands/blockstore/put.ts create mode 100644 packages/rpc-client/src/commands/blockstore/query-keys.ts create mode 100644 packages/rpc-client/src/commands/blockstore/query.ts create mode 100644 packages/rpc-client/src/commands/info.ts create mode 100644 packages/rpc-client/src/commands/utils/rpc-call.ts create mode 100644 packages/rpc-client/src/index.ts create mode 100644 packages/rpc-client/test/index.spec.ts create mode 100644 packages/rpc-client/tsconfig.json create mode 100644 packages/rpc-protocol/LICENSE create mode 100644 packages/rpc-protocol/LICENSE-APACHE create mode 100644 packages/rpc-protocol/LICENSE-MIT create mode 100644 packages/rpc-protocol/README.md create mode 100644 packages/rpc-protocol/package.json create mode 100644 packages/rpc-protocol/src/authorization.proto create mode 100644 packages/rpc-protocol/src/authorization.ts create mode 100644 packages/rpc-protocol/src/blockstore.proto create mode 100644 packages/rpc-protocol/src/blockstore.ts create mode 100644 packages/rpc-protocol/src/datastore.proto create mode 100644 packages/rpc-protocol/src/datastore.ts create mode 100644 packages/rpc-protocol/src/index.ts create mode 100644 packages/rpc-protocol/src/root.proto create mode 100644 packages/rpc-protocol/src/root.ts create mode 100644 packages/rpc-protocol/src/rpc.proto create mode 100644 packages/rpc-protocol/src/rpc.ts create mode 100644 packages/rpc-protocol/tsconfig.json create mode 100644 packages/rpc-server/.aegir.js create mode 100644 packages/rpc-server/LICENSE create mode 100644 packages/rpc-server/LICENSE-APACHE create mode 100644 packages/rpc-server/LICENSE-MIT create mode 100644 packages/rpc-server/README.md create mode 100644 packages/rpc-server/package.json create mode 100644 packages/rpc-server/src/handlers/authorization/get.ts create mode 100644 packages/rpc-server/src/handlers/blockstore/batch.ts create mode 100644 packages/rpc-server/src/handlers/blockstore/close.ts create mode 100644 packages/rpc-server/src/handlers/blockstore/delete-many.ts create mode 100644 packages/rpc-server/src/handlers/blockstore/delete.ts create mode 100644 packages/rpc-server/src/handlers/blockstore/get-many.ts create mode 100644 packages/rpc-server/src/handlers/blockstore/get.ts create mode 100644 packages/rpc-server/src/handlers/blockstore/has.ts create mode 100644 packages/rpc-server/src/handlers/blockstore/open.ts create mode 100644 packages/rpc-server/src/handlers/blockstore/put-many.ts create mode 100644 packages/rpc-server/src/handlers/blockstore/put.ts create mode 100644 packages/rpc-server/src/handlers/blockstore/query-keys.ts create mode 100644 packages/rpc-server/src/handlers/blockstore/query.ts create mode 100644 packages/rpc-server/src/handlers/index.ts create mode 100644 packages/rpc-server/src/handlers/info.ts create mode 100644 packages/rpc-server/src/index.ts create mode 100644 packages/rpc-server/src/utils/multiaddr-to-url.ts create mode 100644 packages/rpc-server/test/index.spec.ts create mode 100644 packages/rpc-server/tsconfig.json create mode 100644 packages/unixfs-cli/.aegir.js create mode 100644 packages/unixfs-cli/LICENSE create mode 100644 packages/unixfs-cli/LICENSE-APACHE create mode 100644 packages/unixfs-cli/LICENSE-MIT create mode 100644 packages/unixfs-cli/README.md create mode 100644 packages/unixfs-cli/package.json create mode 100644 packages/unixfs-cli/src/commands/add.ts create mode 100644 packages/unixfs-cli/src/commands/cat.ts create mode 100644 packages/unixfs-cli/src/commands/index.ts create mode 100644 packages/unixfs-cli/src/commands/stat.ts create mode 100644 packages/unixfs-cli/src/index.ts create mode 100644 packages/unixfs-cli/src/utils/date-to-mtime.ts create mode 100644 packages/unixfs-cli/src/utils/glob-source.ts create mode 100644 packages/unixfs-cli/test/index.spec.ts create mode 100644 packages/unixfs-cli/tsconfig.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0bc3b42 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: +- package-ecosystem: npm + directory: "/" + schedule: + interval: daily + time: "10:00" + open-pull-requests-limit: 10 + commit-message: + prefix: "deps" + prefix-development: "deps(dev)" diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 0000000..d57c2a0 --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,8 @@ +name: Automerge +on: [ pull_request ] + +jobs: + automerge: + uses: protocol/.github/.github/workflows/automerge.yml@master + with: + job: 'automerge' diff --git a/.github/workflows/js-test-and-release.yml b/.github/workflows/js-test-and-release.yml new file mode 100644 index 0000000..27fd45a --- /dev/null +++ b/.github/workflows/js-test-and-release.yml @@ -0,0 +1,143 @@ +name: test & maybe release +on: + push: + branches: + - ${{{ github.default_branch }}} + pull_request: + +jobs: + + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present lint + - run: npm run --if-present dep-check + + test-node: + needs: check + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + node: [lts/*] + fail-fast: true + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:node + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: node + + test-chrome: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:chrome + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: chrome + + test-chrome-webworker: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:chrome-webworker + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: chrome-webworker + + test-firefox: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:firefox + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: firefox + + test-firefox-webworker: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:firefox-webworker + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: firefox-webworker + + test-electron-main: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npx xvfb-maybe npm run --if-present test:electron-main + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: electron-main + + test-electron-renderer: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npx xvfb-maybe npm run --if-present test:electron-renderer + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: electron-renderer + + release: + needs: [test-node, test-chrome, test-chrome-webworker, test-firefox, test-firefox-webworker, test-electron-main, test-electron-renderer] + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/${{{ github.default_branch }}}' + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - uses: ipfs/aegir/actions/docker-login@master + with: + docker-token: ${{ secrets.DOCKER_TOKEN }} + docker-username: ${{ secrets.DOCKER_USERNAME }} + - run: npm run --if-present release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..4c74223 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,152 @@ +name: test & maybe release +on: + push: + branches: + - main + pull_request: + +jobs: + + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present lint + - run: npm run --if-present dep-check + + test-node: + needs: check + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + node: [lts/*] + fail-fast: true + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:node + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: node + + test-chrome: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:chrome + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: chrome + + test-chrome-webworker: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:chrome-webworker + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: chrome-webworker + + test-firefox: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:firefox + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: firefox + + test-firefox-webworker: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:firefox-webworker + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: firefox-webworker + + test-electron-main: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npx xvfb-maybe npm run --if-present test:electron-main + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: electron-main + + test-electron-renderer: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npx xvfb-maybe npm run --if-present test:electron-renderer + - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + flags: electron-renderer + + release: + runs-on: ubuntu-latest + needs: [test-node, test-chrome, test-chrome-webworker, test-firefox, test-firefox-webworker, test-electron-main, test-electron-renderer] + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + steps: + - uses: GoogleCloudPlatform/release-please-action@v2 + id: release + with: + token: ${{ secrets.GITHUB_TOKEN }} + release-type: node + bump-minor-pre-major: true + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + registry-url: 'https://registry.npmjs.org' + - uses: ipfs/aegir/actions/cache-node-modules@master + - if: ${{ steps.release.outputs.release_created }} + name: Run release version + run: npm run --if-present release + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: ${{ !steps.release.outputs.release_created }} + name: Run release rc + run: | + npm run --if-present release:rc + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..6f6d895 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,26 @@ +name: Close and mark stale issue + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + stale: + + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'Oops, seems like we needed more information for this issue, please comment with more details or this issue will be closed in 7 days.' + close-issue-message: 'This issue was closed because it is missing author input.' + stale-issue-label: 'kind/stale' + any-of-labels: 'need/author-input' + exempt-issue-labels: 'need/triage,need/community-input,need/maintainer-input,need/maintainers-input,need/analysis,status/blocked,status/in-progress,status/ready,status/deferred,status/inactive' + days-before-issue-stale: 6 + days-before-issue-close: 7 + enable-statistics: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..910f633 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules +build +dist +.docs +.coverage +node_modules +package-lock.json +yarn.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..20ce483 --- /dev/null +++ b/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,5 @@ +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. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/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. diff --git a/package.json b/package.json new file mode 100644 index 0000000..dfa2c5d --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "helia-cli", + "version": "0.0.0", + "description": "Run helia as a daemon process", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/ipfs/helia-cli#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/ipfs/helia-cli.git" + }, + "bugs": { + "url": "https://github.com/ipfs/helia-cli/issues" + }, + "keywords": [ + "ipfs" + ], + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "private": true, + "scripts": { + "reset": "aegir run clean && aegir clean **/node_modules **/package-lock.json", + "test": "aegir run test", + "test:node": "aegir run test:node", + "test:chrome": "aegir run test:chrome", + "test:chrome-webworker": "aegir run test:chrome-webworker", + "test:firefox": "aegir run test:firefox", + "test:firefox-webworker": "aegir run test:firefox-webworker", + "test:electron-main": "aegir run test:electron-main", + "test:electron-renderer": "aegir run test:electron-renderer", + "clean": "aegir run clean", + "generate": "aegir run generate", + "build": "aegir run build", + "lint": "aegir run lint", + "docs": "NODE_OPTIONS=--max_old_space_size=4096 aegir docs", + "docs:no-publish": "npm run docs -- --publish false", + "dep-check": "aegir run dep-check", + "release": "npm run docs:no-publish && npm run release:npm && npm run docs", + "release:npm": "aegir exec npm -- publish", + "release:rc": "aegir release-rc" + }, + "dependencies": { + "aegir": "^38.1.0" + }, + "type": "module", + "workspaces": [ + "packages/*" + ] +} diff --git a/packages/cli-utils/.aegir.js b/packages/cli-utils/.aegir.js new file mode 100644 index 0000000..e9c18f3 --- /dev/null +++ b/packages/cli-utils/.aegir.js @@ -0,0 +1,6 @@ + +export default { + build: { + bundle: false + } +} diff --git a/packages/cli-utils/LICENSE b/packages/cli-utils/LICENSE new file mode 100644 index 0000000..20ce483 --- /dev/null +++ b/packages/cli-utils/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/cli-utils/LICENSE-APACHE b/packages/cli-utils/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/packages/cli-utils/LICENSE-APACHE @@ -0,0 +1,5 @@ +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. diff --git a/packages/cli-utils/LICENSE-MIT b/packages/cli-utils/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/packages/cli-utils/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/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. diff --git a/packages/cli-utils/README.md b/packages/cli-utils/README.md new file mode 100644 index 0000000..0e90e7d --- /dev/null +++ b/packages/cli-utils/README.md @@ -0,0 +1,44 @@ +# @helia/cli-utils + +[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) +[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech) +[![codecov](https://img.shields.io/codecov/c/github/ipfs/helia-cli.svg?style=flat-square)](https://codecov.io/gh/ipfs/helia-cli) +[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/helia-cli/js-test-and-release.yml?branch=main\&style=flat-square)](https://github.com/ipfs/helia-cli/actions/workflows/js-test-and-release.yml?query=branch%3Amain) + +> Common code for Helia CLI tools + +## Table of contents + +- [Install](#install) +- [API Docs](#api-docs) +- [License](#license) +- [Contribute](#contribute) + +## Install + +```console +$ npm i @helia/cli-utils +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribute + +Contributions welcome! Please check out [the issues](https://github.com/ipfs/helia-cli/issues). + +Also see our [contributing document](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) for more information on how we work, and about contributing in general. + +Please be aware that all interactions related to this repo are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) diff --git a/packages/cli-utils/package.json b/packages/cli-utils/package.json new file mode 100644 index 0000000..a74cf54 --- /dev/null +++ b/packages/cli-utils/package.json @@ -0,0 +1,199 @@ +{ + "name": "@helia/cli-utils", + "version": "0.0.0", + "description": "Common code for Helia CLI tools", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/ipfs/helia-cli/tree/master/packages/cli-utils#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/ipfs/helia-cli.git" + }, + "bugs": { + "url": "https://github.com/ipfs/helia-cli/issues" + }, + "keywords": [ + "IPFS" + ], + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "type": "module", + "types": "./dist/src/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ], + "src/*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ] + } + }, + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + }, + "./create-helia": { + "types": "./dist/src/create-helia.d.ts", + "import": "./dist/src/create-helia.js" + }, + "./find-helia": { + "types": "./dist/src/find-helia.d.ts", + "import": "./dist/src/find-helia.js" + }, + "./format": { + "types": "./dist/src/format.d.ts", + "import": "./dist/src/format.js" + }, + "./load-rpc-keychain": { + "types": "./dist/src/load-rpc-keychain.d.ts", + "import": "./dist/src/load-rpc-keychain.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "main" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "test": "aegir test", + "test:node": "aegir test -t node --cov", + "release": "aegir release" + }, + "dependencies": { + "@chainsafe/libp2p-gossipsub": "^6.0.0", + "@chainsafe/libp2p-noise": "^11.0.0", + "@chainsafe/libp2p-yamux": "^3.0.3", + "@helia/interface": "next", + "@helia/rpc-client": "~0.0.0", + "@libp2p/bootstrap": "^6.0.0", + "@libp2p/interface-keychain": "^2.0.3", + "@libp2p/interface-peer-id": "^2.0.1", + "@libp2p/kad-dht": "^7.0.0", + "@libp2p/keychain": "^1.0.0", + "@libp2p/logger": "^2.0.5", + "@libp2p/mplex": "^7.1.1", + "@libp2p/prometheus-metrics": "1.1.3", + "@libp2p/tcp": "^6.0.8", + "@libp2p/websockets": "^5.0.2", + "@multiformats/multiaddr": "^11.1.5", + "@ucans/ucans": "^0.11.0-alpha", + "blockstore-datastore-adapter": "^5.0.0", + "datastore-core": "^8.0.4", + "datastore-fs": "^8.0.0", + "helia": "next", + "kleur": "^4.1.5", + "libp2p": "^0.42.2", + "strip-json-comments": "^5.0.0" + }, + "devDependencies": { + "aegir": "^38.1.0" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/cli-utils/src/create-helia.ts b/packages/cli-utils/src/create-helia.ts new file mode 100644 index 0000000..7ebe2da --- /dev/null +++ b/packages/cli-utils/src/create-helia.ts @@ -0,0 +1,89 @@ +import type { Helia } from '@helia/interface' +import type { HeliaConfig } from './index.js' +import { createHelia as createHeliaNode } from 'helia' +import { FsDatastore } from 'datastore-fs' +import { BlockstoreDatastoreAdapter } from 'blockstore-datastore-adapter' +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { webSockets } from '@libp2p/websockets' +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { mplex } from '@libp2p/mplex' +import { prometheusMetrics } from '@libp2p/prometheus-metrics' +import { gossipsub } from '@chainsafe/libp2p-gossipsub' +import { kadDHT } from '@libp2p/kad-dht' +import { bootstrap } from '@libp2p/bootstrap' +import stripJsonComments from 'strip-json-comments' +import fs from 'node:fs' +import path from 'node:path' +import * as readline from 'node:readline/promises' +import { ShardingDatastore } from 'datastore-core' +import { NextToLast } from 'datastore-core/shard' + +export async function createHelia (configDir: string, offline: boolean = false): Promise { + const config: HeliaConfig = JSON.parse(stripJsonComments(fs.readFileSync(path.join(configDir, 'helia.json'), 'utf-8'))) + let password = config.libp2p.keychain.password + + if (config.libp2p.keychain.password == null) { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }) + password = await rl.question('Enter libp2p keychain password: ') + } + + const datastore = new FsDatastore(config.datastore, { + createIfMissing: true + }) + await datastore.open() + + const blockstore = new BlockstoreDatastoreAdapter( + new ShardingDatastore( + new FsDatastore(config.blockstore), + new NextToLast(2) + ) + ) + await blockstore.open() + + const helia = await createHeliaNode({ + blockstore, + datastore, + libp2p: await createLibp2p({ + start: !offline, + datastore, + addresses: config.libp2p.addresses, + identify: { + host: { + agentVersion: 'helia/0.0.0' + } + }, + keychain: { + pass: password, + dek: { + salt: config.libp2p.keychain.salt + } + }, + transports: [ + tcp(), + webSockets() + ], + connectionEncryption: [ + noise() + ], + streamMuxers: [ + yamux(), + mplex() + ], + peerDiscovery: [ + bootstrap({ + list: config.libp2p.bootstrap + }) + ], + pubsub: gossipsub(), + dht: kadDHT(), + metrics: prometheusMetrics() + }) + }) + + return helia +} diff --git a/packages/cli-utils/src/find-helia-dir.ts b/packages/cli-utils/src/find-helia-dir.ts new file mode 100644 index 0000000..2a60f51 --- /dev/null +++ b/packages/cli-utils/src/find-helia-dir.ts @@ -0,0 +1,16 @@ +import os from 'node:os' +import path from 'node:path' + +export function findHeliaDir (): string { + if (process.env.XDG_DATA_HOME != null) { + return process.env.XDG_DATA_HOME + } + + const platform = os.platform() + + if (platform === 'darwin') { + return path.join(`${process.env.HOME}`, 'Library', 'helia') + } + + return path.join(`${process.env.HOME}`, '.helia') +} diff --git a/packages/cli-utils/src/find-helia.ts b/packages/cli-utils/src/find-helia.ts new file mode 100644 index 0000000..ea7237a --- /dev/null +++ b/packages/cli-utils/src/find-helia.ts @@ -0,0 +1,148 @@ +import type { Helia } from '@helia/interface' +import { createHeliaRpcClient } from '@helia/rpc-client' +import { multiaddr } from '@multiformats/multiaddr' +import { createHelia } from './create-helia.js' +import { createLibp2p, Libp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { mplex } from '@libp2p/mplex' +import { logger } from '@libp2p/logger' +import fs from 'node:fs' +import path from 'node:path' +import os from 'node:os' +import { FsDatastore } from 'datastore-fs' +import { loadRpcKeychain } from './load-rpc-keychain.js' +import type { PeerId } from '@libp2p/interface-peer-id' + +const log = logger('helia:cli:utils:find-helia') + +export async function findHelia (configDir: string, rpcAddress: string, user: string, offline: boolean = true, online: boolean = true): Promise<{ helia: Helia, libp2p: Libp2p | undefined }> { + let { + libp2p, helia + } = await findOnlineHelia(configDir, rpcAddress, user) + + if (helia == null) { + log('connecting to running helia node failed') + + if (!offline) { + log('could not connect to running helia node and command cannot be run in offline mode') + throw new Error('Could not connect to Helia - is the node running?') + } + + log('create offline helia node') + helia = await createHelia(configDir, offline) + } else if (!online) { + log('connected to running helia node but command cannot be run in online mode') + throw new Error('This command cannot be run while a Helia daemon is running') + } + + return { + helia, + libp2p + } +} + +export async function findOnlineHelia (configDir: string, rpcAddress: string, user: string): Promise<{ helia?: Helia, libp2p?: Libp2p }> { + const isRunning = await isHeliaRunning(configDir) + + if (!isRunning) { + log('helia daemon was not running') + return {} + } + + let peerId: PeerId | undefined + + try { + const rpcKeychain = await loadRpcKeychain(configDir) + peerId = await rpcKeychain.exportPeerId(`rpc-user-${user}`) + } catch (err) { + log('could not load peer id rpc-user-%s', user, err) + } + + log('create dial-only libp2p node') + const libp2p = await createLibp2p({ + peerId, + datastore: new FsDatastore(path.join(configDir, 'rpc')), + transports: [ + tcp() + ], + connectionEncryption: [ + noise() + ], + streamMuxers: [ + yamux(), + mplex() + ], + relay: { + enabled: false + }, + nat: { + enabled: false + } + }) + + let helia: Helia | undefined + + try { + log('create helia client') + helia = await createHeliaRpcClient({ + multiaddr: multiaddr(`/unix/${rpcAddress}`), + libp2p, + user + }) + } catch (err: any) { + log('could not create helia rpc client', err) + await libp2p.stop() + + if (err.name === 'AggregateError' && err.errors != null) { + throw err.errors[0] + } else { + throw err + } + } + + return { + helia, + libp2p + } +} + +export async function isHeliaRunning (configDir: string): Promise { + const pidFilePath = path.join(configDir, 'helia.pid') + + if (!fs.existsSync(pidFilePath)) { + log('pidfile at %s did not exist', pidFilePath) + return false + } + + const pid = Number(fs.readFileSync(pidFilePath, { + encoding: 'utf8' + }).trim()) + + if (isNaN(pid)) { + log('pidfile at %s had invalid contents', pidFilePath) + log('removing invalid pidfile') + fs.rmSync(pidFilePath) + return false + } + + try { + // this will throw if the process does not exist + os.getPriority(pid) + return true + } catch (err: any) { + log('getting process info for pid %d failed', pid) + + if (err.message.includes('no such process') === true) { + log('process for pid %d was not running', pid) + log('removing stale pidfile') + fs.rmSync(pidFilePath) + + return false + } + + log('error getting process priority for pid %d', pid, err) + throw err + } +} diff --git a/packages/cli-utils/src/format.ts b/packages/cli-utils/src/format.ts new file mode 100644 index 0000000..010d068 --- /dev/null +++ b/packages/cli-utils/src/format.ts @@ -0,0 +1,75 @@ +import kleur from 'kleur' + +export function formatter (stdout: NodeJS.WriteStream, items: Formatable[]): void { + items.forEach(item => stdout.write(item())) +} + +export interface Formatable { + (): string +} + +export function header (string: string): Formatable { + return (): string => { + return `\n${string}\n` + } +} + +export function subheader (string: string): Formatable { + return (): string => { + return `\n${string}\n` + } +} + +export function paragraph (string: string): Formatable { + return (): string => { + return kleur.white(`\n${string}\n`) + } +} + +export function table (rows: FormatableRow[]): Formatable { + const cellLengths: string[] = [] + + for (const row of rows) { + const cells = row() + + for (let i = 0; i < cells.length; i++) { + const textLength = cells[i].length + + if (cellLengths[i] == null || cellLengths[i].length < textLength) { + cellLengths[i] = new Array(textLength).fill(' ').join('') + } + } + } + + return (): string => { + const output: string[] = [] + + for (const row of rows) { + const cells = row() + const text: string[] = [] + + for (let i = 0; i < cells.length; i++) { + const cell = cells[i] + text.push((cell + cellLengths[i]).substring(0, cellLengths[i].length)) + } + + output.push(text.join(' ') + '\n') + } + + return output.join('') + } +} + +export interface FormatableRow { + rowLengths: number[] + (): string[] +} + +export function row (...cells: string[]): FormatableRow { + const formatable = (): string[] => { + return cells + } + formatable.rowLengths = cells.map(str => str.length) + + return formatable +} diff --git a/packages/cli-utils/src/generate-auth.ts b/packages/cli-utils/src/generate-auth.ts new file mode 100644 index 0000000..3b6da3a --- /dev/null +++ b/packages/cli-utils/src/generate-auth.ts @@ -0,0 +1,21 @@ +import { EdKeypair, build, encode } from '@ucans/ucans' + +export async function generateAuth (serverKey: string): Promise { + const issuer = EdKeypair.fromSecretKey(serverKey, { + format: 'base64url' + }) + + const userKey = await EdKeypair.create() + + const clientUcan = await build({ + issuer, + audience: userKey.did(), + expiration: (Date.now() / 1000) + (60 * 60 * 24), + capabilities: [{ + with: { scheme: 'service', hierPart: '/cat' }, + can: { namespace: 'service', segments: ['GET'] } + }] + }) + + return encode(clientUcan) +} diff --git a/packages/cli-utils/src/index.ts b/packages/cli-utils/src/index.ts new file mode 100644 index 0000000..b46c753 --- /dev/null +++ b/packages/cli-utils/src/index.ts @@ -0,0 +1,304 @@ +import type { ParseArgsConfig } from 'node:util' +import type { Helia } from '@helia/interface' +import { InvalidParametersError } from '@helia/interface/errors' +import { parseArgs } from 'node:util' +import { findHeliaDir } from './find-helia-dir.js' +import path from 'node:path' +import { printHelp } from './print-help.js' +import fs from 'node:fs' +import { findHelia } from './find-helia.js' +import type { Libp2p } from 'libp2p' + +/** + * Extends the internal node type to add a description to the options + */ +export interface ParseArgsOptionConfig { + /** + * Type of argument. + */ + type: 'string' | 'boolean' + + /** + * Whether this option can be provided multiple times. + * If `true`, all values will be collected in an array. + * If `false`, values for the option are last-wins. + * + * @default false. + */ + multiple?: boolean + + /** + * A single character alias for the option. + */ + short?: string + + /** + * The default option value when it is not set by args. + * It must be of the same type as the the `type` property. + * When `multiple` is `true`, it must be an array. + * + * @since v18.11.0 + */ + default?: string | boolean | string[] | boolean[] + + /** + * A description used to generate help text + */ + description: string + + /** + * If specified the value must be in this list + */ + valid?: string[] +} + +type ParseArgsOptionsConfig = Record + +export interface CommandOptions extends ParseArgsConfig { + /** + * Used to describe arguments known to the parser. + */ + options?: T +} + +export interface Command { + /** + * The command name + */ + command: string + + /** + * Used to generate help text + */ + description: string + + /** + * Used to generate help text + */ + example?: string + + /** + * Specify if this command can be run offline (default true) + */ + offline?: boolean + + /** + * Specify if this command can be run online (default true) + */ + online?: boolean + + /** + * Configuration for the command + */ + options?: ParseArgsOptionsConfig + + /** + * Run the command + */ + execute: (ctx: Context & T) => Promise + + /** + * Subcommands of the current command + */ + subcommands?: Array> +} + +export interface Context { + helia: Helia + directory: string + stdin: NodeJS.ReadStream + stdout: NodeJS.WriteStream + stderr: NodeJS.WriteStream +} + +export function createCliConfig (options?: T, strict?: boolean): ParseArgsConfig { + return { + allowPositionals: true, + strict: strict ?? true, + options: { + help: { + // @ts-expect-error description field not defined + description: 'Show help text', + type: 'boolean' + }, + ...options + } + } +} + +/** + * Typedef for the Helia config file + */ +export interface HeliaConfig { + blockstore: string + datastore: string + libp2p: { + addresses: { + listen: string[] + announce: string[] + noAnnounce: string[] + } + keychain: { + salt: string + password?: string + } + bootstrap: string[] + } + rpc: { + datastore: string + keychain: { + salt: string + password?: string + } + } +} + +export interface RootArgs { + positionals: string[] + directory: string + help: boolean + rpcAddress: string + user: string +} + +const root: Command = { + command: '', + description: '', + options: { + directory: { + description: 'The directory used by Helia to store config and data', + type: 'string', + default: findHeliaDir() + }, + rpcAddress: { + description: 'The multiaddr of the Helia node', + type: 'string', + default: path.join(findHeliaDir(), 'rpc.sock') + }, + user: { + description: 'The name of the RPC user', + type: 'string', + default: process.env.USER + } + }, + async execute () {} +} + +export async function cli (command: string, description: string, subcommands: Array>): Promise { + const rootCommand: Command = { + ...root, + command, + description, + subcommands + } + const config = createCliConfig(rootCommand.options, false) + const rootCommandArgs = parseArgs(config) + const configDir = rootCommandArgs.values.directory + + if (configDir == null || typeof configDir !== 'string') { + throw new InvalidParametersError('No config directory specified') + } + + if (typeof rootCommandArgs.values.rpcAddress !== 'string') { + throw new InvalidParametersError('No RPC address specified') + } + + if (typeof rootCommandArgs.values.user !== 'string') { + throw new InvalidParametersError('No RPC user specified') + } + + if (rootCommandArgs.values.help === true && rootCommandArgs.positionals.length === 0) { + printHelp(rootCommand, process.stdout) + return + } + + if (!fs.existsSync(configDir)) { + const init = subcommands.find(command => command.command === 'init') + + if (init == null) { + throw new Error('Could not find init command') + } + + // run the init command + const parsed = parseArgs(createCliConfig(init.options, false)) + + if (parsed.values.help === true) { + printHelp(init, process.stdout) + return + } + + await init.execute({ + ...parsed.values, + positionals: parsed.positionals.slice(1), + stdin: process.stdin, + stdout: process.stdout, + stderr: process.stderr, + directory: configDir + }) + + if (rootCommandArgs.positionals[0] === 'init') { + // if init was specified explicitly we can bail because we just ran init + return + } + } + + if (rootCommandArgs.positionals.length > 0) { + let subCommand: Command = rootCommand + let subCommandDepth = 0 + + for (let i = 0; i < rootCommandArgs.positionals.length; i++) { + const positional = rootCommandArgs.positionals[i] + + if (subCommand.subcommands == null) { + break + } + + const sub = subCommand.subcommands.find(c => c.command === positional) + + if (sub != null) { + subCommandDepth++ + subCommand = sub + } + } + + if (subCommand == null) { + throw new Error('Command not found') + } + + const subCommandArgs = parseArgs(createCliConfig(subCommand.options)) + + if (subCommandArgs.values.help === true) { + printHelp(subCommand, process.stdout) + return + } + + let helia: Helia | undefined + let libp2p: Libp2p | undefined + + if (subCommand.command !== 'daemon' && subCommand.command !== 'status') { + const res = await findHelia(configDir, rootCommandArgs.values.rpcAddress, rootCommandArgs.values.user, subCommand.offline, subCommand.online) + helia = res.helia + libp2p = res.libp2p + } + + await subCommand.execute({ + ...rootCommandArgs.values, + ...subCommandArgs.values, + positionals: subCommandArgs.positionals.slice(subCommandDepth), + helia, + stdin: process.stdin, + stdout: process.stdout, + stderr: process.stderr, + directory: configDir + }) + + if (libp2p != null) { + await libp2p.stop() + } + + return + } + + // no command specified, print help + printHelp(rootCommand, process.stdout) +} diff --git a/packages/cli-utils/src/load-rpc-keychain.ts b/packages/cli-utils/src/load-rpc-keychain.ts new file mode 100644 index 0000000..a0fea50 --- /dev/null +++ b/packages/cli-utils/src/load-rpc-keychain.ts @@ -0,0 +1,38 @@ +import type { HeliaConfig } from './index.js' +import { FsDatastore } from 'datastore-fs' +import stripJsonComments from 'strip-json-comments' +import fs from 'node:fs' +import path from 'node:path' +import * as readline from 'node:readline/promises' +import { DefaultKeyChain } from '@libp2p/keychain' +import type { KeyChain } from '@libp2p/interface-keychain' + +export async function loadRpcKeychain (configDir: string): Promise { + const config: HeliaConfig = JSON.parse(stripJsonComments(fs.readFileSync(path.join(configDir, 'helia.json'), 'utf-8'))) + const datastore = new FsDatastore(config.rpc.datastore, { + createIfMissing: true + }) + await datastore.open() + + let password = config.rpc.keychain.password + + if (password == null) { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }) + password = await rl.question('Enter libp2p keychain password: ') + } + + return new DefaultKeyChain({ + datastore + }, { + pass: password, + dek: { + keyLength: 512 / 8, + iterationCount: 10000, + hash: 'sha2-512', + salt: config.rpc.keychain.salt + } + }) +} diff --git a/packages/cli-utils/src/print-help.ts b/packages/cli-utils/src/print-help.ts new file mode 100644 index 0000000..72b4ba3 --- /dev/null +++ b/packages/cli-utils/src/print-help.ts @@ -0,0 +1,47 @@ +import type { Command } from './index.js' +import * as format from './format.js' +import type { Formatable } from './format.js' +import kleur from 'kleur' + +export function printHelp (command: Command, stdout: NodeJS.WriteStream): void { + const items: Formatable[] = [ + format.header(command.description) + ] + + if (command.example != null) { + items.push( + format.subheader('Example:'), + format.paragraph(command.example) + ) + } + + if (command.subcommands != null) { + items.push( + format.subheader('Subcommands:'), + format.table( + command.subcommands.map(command => format.row( + ` ${command.command}`, + kleur.white(command.description) + )) + ) + ) + } + + if (command.options != null) { + items.push( + format.subheader('Options:'), + format.table( + Object.entries(command.options).map(([key, option]) => format.row( + ` --${key}`, + kleur.white(option.description), + option.default != null ? kleur.grey(`[default: ${option.default}]`) : '' + )) + ) + ) + } + + format.formatter( + stdout, + items + ) +} diff --git a/packages/cli-utils/test/index.spec.ts b/packages/cli-utils/test/index.spec.ts new file mode 100644 index 0000000..e67dc48 --- /dev/null +++ b/packages/cli-utils/test/index.spec.ts @@ -0,0 +1,7 @@ +import { expect } from 'aegir/chai' + +describe('cli', () => { + it('should start a node', () => { + expect(true).to.be.ok() + }) +}) diff --git a/packages/cli-utils/tsconfig.json b/packages/cli-utils/tsconfig.json new file mode 100644 index 0000000..e2fc97b --- /dev/null +++ b/packages/cli-utils/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../rpc-client" + } + ] +} diff --git a/packages/helia-cli/.aegir.js b/packages/helia-cli/.aegir.js new file mode 100644 index 0000000..e9c18f3 --- /dev/null +++ b/packages/helia-cli/.aegir.js @@ -0,0 +1,6 @@ + +export default { + build: { + bundle: false + } +} diff --git a/packages/helia-cli/LICENSE b/packages/helia-cli/LICENSE new file mode 100644 index 0000000..20ce483 --- /dev/null +++ b/packages/helia-cli/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/helia-cli/LICENSE-APACHE b/packages/helia-cli/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/packages/helia-cli/LICENSE-APACHE @@ -0,0 +1,5 @@ +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. diff --git a/packages/helia-cli/LICENSE-MIT b/packages/helia-cli/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/packages/helia-cli/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/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. diff --git a/packages/helia-cli/README.md b/packages/helia-cli/README.md new file mode 100644 index 0000000..db07faa --- /dev/null +++ b/packages/helia-cli/README.md @@ -0,0 +1,44 @@ +# @helia/cli + +[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) +[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech) +[![codecov](https://img.shields.io/codecov/c/github/ipfs/helia-cli.svg?style=flat-square)](https://codecov.io/gh/ipfs/helia-cli) +[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/helia-cli/js-test-and-release.yml?branch=main\&style=flat-square)](https://github.com/ipfs/helia-cli/actions/workflows/js-test-and-release.yml?query=branch%3Amain) + +> Run a Helia node on the cli + +## Table of contents + +- [Install](#install) +- [API Docs](#api-docs) +- [License](#license) +- [Contribute](#contribute) + +## Install + +```console +$ npm i @helia/cli +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribute + +Contributions welcome! Please check out [the issues](https://github.com/ipfs/helia-cli/issues). + +Also see our [contributing document](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) for more information on how we work, and about contributing in general. + +Please be aware that all interactions related to this repo are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) diff --git a/packages/helia-cli/package.json b/packages/helia-cli/package.json new file mode 100644 index 0000000..580bf41 --- /dev/null +++ b/packages/helia-cli/package.json @@ -0,0 +1,158 @@ +{ + "name": "@helia/cli", + "version": "0.0.0", + "description": "Run a Helia node on the cli", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/ipfs/helia-cli/tree/master/packages/helia-cli#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/ipfs/helia-cli.git" + }, + "bugs": { + "url": "https://github.com/ipfs/helia-cli/issues" + }, + "keywords": [ + "IPFS" + ], + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "bin": { + "helia": "./dist/src/index.js" + }, + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "main" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "test": "aegir test", + "test:node": "aegir test -t node --cov", + "release": "aegir release" + }, + "dependencies": { + "@helia/cli-utils": "~0.0.0", + "@helia/interface": "next", + "@helia/rpc-server": "~0.0.0", + "@libp2p/crypto": "^1.0.11", + "@libp2p/interface-keychain": "^2.0.3", + "@libp2p/interface-peer-id": "^2.0.1", + "@libp2p/keychain": "^1.0.0", + "@libp2p/logger": "^2.0.5", + "@libp2p/peer-id-factory": "^2.0.0", + "datastore-fs": "^8.0.0", + "kleur": "^4.1.5", + "uint8arrays": "^4.0.3" + }, + "devDependencies": { + "aegir": "^38.1.0" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/helia-cli/src/commands/daemon.ts b/packages/helia-cli/src/commands/daemon.ts new file mode 100644 index 0000000..5fad7e0 --- /dev/null +++ b/packages/helia-cli/src/commands/daemon.ts @@ -0,0 +1,96 @@ +import type { Command } from '@helia/cli-utils' +import { createHelia } from '@helia/cli-utils/create-helia' +import { createHeliaRpcServer } from '@helia/rpc-server' +import fs from 'node:fs' +import path from 'node:path' +import os from 'node:os' +import { logger } from '@libp2p/logger' +import { loadRpcKeychain } from '@helia/cli-utils/load-rpc-keychain' + +const log = logger('helia:cli:commands:daemon') + +interface DaemonArgs { + positionals?: string[] + authorizationValiditySeconds: number +} + +export const daemon: Command = { + command: 'daemon', + description: 'Starts a Helia daemon', + example: '$ helia daemon', + online: false, + options: { + authorizationValiditySeconds: { + description: 'How many seconds a request authorization token is valid for', + type: 'string', + default: '5' + } + }, + async execute ({ directory, stdout, authorizationValiditySeconds }) { + const lockfilePath = path.join(directory, 'helia.pid') + checkPidFile(lockfilePath) + + const rpcSocketFilePath = path.join(directory, 'rpc.sock') + checkRpcSocketFile(rpcSocketFilePath) + + const helia = await createHelia(directory) + + await createHeliaRpcServer({ + helia, + users: await loadRpcKeychain(directory), + authorizationValiditySeconds: Number(authorizationValiditySeconds) + }) + + const info = await helia.info() + + stdout.write(`${info.agentVersion} is running\n`) + + if (info.multiaddrs.length > 0) { + stdout.write('Listening on:\n') + + info.multiaddrs.forEach(ma => { + stdout.write(` ${ma.toString()}\n`) + }) + } + + fs.writeFileSync(lockfilePath, process.pid.toString()) + } +} + +/** + * Check the passed lockfile path exists, if it does it should contain the PID + * of the owning process. Read the file, check if the process with the PID is + * still running, throw an error if it is. + * + * @param pidFilePath + */ +function checkPidFile (pidFilePath: string): void { + if (!fs.existsSync(pidFilePath)) { + return + } + + const pid = Number(fs.readFileSync(pidFilePath, { + encoding: 'utf8' + }).trim()) + + try { + // this will throw if the process does not exist + os.getPriority(pid) + + throw new Error(`Helia already running with pid ${pid}`) + } catch (err: any) { + if (err.message.includes('no such process') === true) { + log('Removing stale pidfile') + fs.rmSync(pidFilePath) + } else { + throw err + } + } +} + +function checkRpcSocketFile (rpcSocketFilePath: string): void { + if (fs.existsSync(rpcSocketFilePath)) { + log('Removing stale rpc socket file') + fs.rmSync(rpcSocketFilePath) + } +} diff --git a/packages/helia-cli/src/commands/id.ts b/packages/helia-cli/src/commands/id.ts new file mode 100644 index 0000000..7c61955 --- /dev/null +++ b/packages/helia-cli/src/commands/id.ts @@ -0,0 +1,16 @@ +import type { Command } from '@helia/cli-utils' + +interface IdArgs { + positionals?: string[] +} + +export const id: Command = { + command: 'id', + description: 'Print information out this Helia node', + example: '$ helia id', + async execute ({ helia, stdout }) { + const result = await helia.info() + + stdout.write(JSON.stringify(result, null, 2) + '\n') + } +} diff --git a/packages/helia-cli/src/commands/index.ts b/packages/helia-cli/src/commands/index.ts new file mode 100644 index 0000000..022c9ab --- /dev/null +++ b/packages/helia-cli/src/commands/index.ts @@ -0,0 +1,12 @@ +import { init } from './init.js' +import { daemon } from './daemon.js' +import { id } from './id.js' +import { status } from './status.js' +import type { Command } from '@helia/cli-utils' + +export const commands: Array> = [ + init, + daemon, + id, + status +] diff --git a/packages/helia-cli/src/commands/init.ts b/packages/helia-cli/src/commands/init.ts new file mode 100644 index 0000000..6440077 --- /dev/null +++ b/packages/helia-cli/src/commands/init.ts @@ -0,0 +1,253 @@ +import type { Command } from '@helia/cli-utils' +import path from 'node:path' +import fs from 'node:fs/promises' +import { createEd25519PeerId, createRSAPeerId, createSecp256k1PeerId } from '@libp2p/peer-id-factory' +import { InvalidParametersError } from '@helia/interface/errors' +import type { PeerId } from '@libp2p/interface-peer-id' +import { logger } from '@libp2p/logger' +import { FsDatastore } from 'datastore-fs' +import { randomBytes } from '@libp2p/crypto' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { DefaultKeyChain } from '@libp2p/keychain' +import type { KeyType } from '@libp2p/interface-keychain' +import { loadRpcKeychain } from '@helia/cli-utils/load-rpc-keychain' + +const log = logger('helia:cli:commands:init') + +interface InitArgs { + positionals?: string[] + keyType: string + bits: string + port: string + directory: string + directoryMode: string + configFileMode: string + publicKeyMode: string + keychainPassword: string + keychainSalt: string + storePassword: boolean + rpcKeychainPassword: string + rpcKeychainSalt: string + storeRpcPassword: boolean + rpcUser: string + rpcUserKeyType: KeyType +} + +// NIST SP 800-132 +const NIST_MINIMUM_SALT_LENGTH = 128 / 8 +const SALT_LENGTH = Math.ceil(NIST_MINIMUM_SALT_LENGTH / 3) * 3 // no base64 padding + +export const init: Command = { + command: 'init', + online: false, + description: 'Initialize the node', + example: '$ helia init', + options: { + keyType: { + description: 'The key type, valid options are "ed25519", "secp256k1" or "rsa"', + type: 'string', + short: 'k', + default: 'ed25519' + }, + bits: { + description: 'Key length (only applies to RSA keys)', + type: 'string', + short: 'b', + default: '2048' + }, + directoryMode: { + description: 'Create the data directory with this mode', + type: 'string', + default: '0700' + }, + configFileMode: { + description: 'Create the config file with this mode', + type: 'string', + default: '0600' + }, + publicKeyMode: { + description: 'Create the public key file with this mode', + type: 'string', + default: '0644' + }, + keychainPassword: { + description: 'The libp2p keychain will use a key derived from this password for encryption operations', + type: 'string', + default: uint8ArrayToString(randomBytes(20), 'base64') + }, + keychainSalt: { + description: 'The libp2p keychain will use use this salt when deriving the key from the password', + type: 'string', + default: uint8ArrayToString(randomBytes(SALT_LENGTH), 'base64') + }, + storePassword: { + description: 'If true, store the password used to derive the key used by the libp2p keychain in the config file', + type: 'boolean', + default: true + }, + rpcKeychainPassword: { + description: 'The RPC server keychain will use a key derived from this password for encryption operations', + type: 'string', + default: uint8ArrayToString(randomBytes(20), 'base64') + }, + rpcKeychainSalt: { + description: 'The RPC server keychain will use use this salt when deriving the key from the password', + type: 'string', + default: uint8ArrayToString(randomBytes(SALT_LENGTH), 'base64') + }, + storeRpcPassword: { + description: 'If true, store the password used to derive the key used by the RPC server keychain in the config file', + type: 'boolean', + default: true + }, + rpcUser: { + description: 'The default RPC user', + type: 'string', + default: process.env.USER + }, + rpcUserKeyType: { + description: 'The default RPC user key tupe', + type: 'string', + default: 'Ed25519', + valid: ['RSA', 'Ed25519', 'secp256k1'] + } + }, + async execute ({ keyType, bits, directory, directoryMode, configFileMode, publicKeyMode, stdout, keychainPassword, keychainSalt, storePassword, rpcKeychainPassword, rpcKeychainSalt, storeRpcPassword, rpcUser, rpcUserKeyType }) { + try { + await fs.readdir(directory) + // don't init if we are already inited + throw new InvalidParametersError(`Cowardly refusing to reinitialize Helia at ${directory}`) + } catch (err: any) { + if (err.code !== 'ENOENT') { + throw err + } + } + + const configFilePath = path.join(directory, 'helia.json') + + try { + await fs.access(configFilePath) + // don't init if we are already inited + throw new InvalidParametersError(`Cowardly refusing to overwrite Helia config file at ${configFilePath}`) + } catch (err: any) { + if (err.code !== 'ENOENT') { + throw err + } + } + + const peerId = await generateKey(keyType, bits) + + if (peerId.publicKey == null || peerId.privateKey == null) { + throw new InvalidParametersError('Generated PeerId had missing components') + } + + log('create helia dir %s', directory) + await fs.mkdir(directory, { + recursive: true, + mode: parseInt(directoryMode, 8) + }) + + const datastorePath = path.join(directory, 'data') + const rpcDatastorePath = path.join(directory, 'rpc') + + // create a dial-only libp2p node configured with the datastore in the helia + // directory - this will store the peer id securely in the keychain + const datastore = new FsDatastore(datastorePath, { + createIfMissing: true + }) + await datastore.open() + const keychain = new DefaultKeyChain({ + datastore + }, { + pass: keychainPassword, + dek: { + keyLength: 512 / 8, + iterationCount: 10000, + hash: 'sha2-512', + salt: keychainSalt + } + }) + await keychain.importPeer('self', peerId) + await datastore.close() + + // now write the public key from the PeerId out for use by the RPC client + const publicKeyPath = path.join(directory, 'peer.pub') + log('create public key %s', publicKeyPath) + await fs.writeFile(publicKeyPath, peerId.toString() + '\n', { + mode: parseInt(publicKeyMode, 8), + flag: 'ax' + }) + + log('create config file %s', configFilePath) + await fs.writeFile(configFilePath, ` +{ + // Where blocks are stored + "blockstore": "${path.join(directory, 'blocks')}", + + // Where data is stored + "datastore": "${datastorePath}", + + // libp2p configuration + "libp2p": { + "addresses": { + "listen": [ + "/ip4/0.0.0.0/tcp/0", + "/ip4/0.0.0.0/tcp/0/ws", + + // this is the rpc socket + "/unix${directory}/rpc.sock" + ], + "noAnnounce": [ + // do not announce the rpc socket to the outside world + "/unix${directory}/rpc.sock" + ] + }, + "keychain": { + "salt": "${keychainSalt}"${storePassword +? `, + "password": "${keychainPassword}"` +: ''} + }, + "bootstrap": [ + "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt" + ] + }, + "rpc": { + "datastore": "${rpcDatastorePath}", + "keychain": { + "salt": "${rpcKeychainSalt}"${storeRpcPassword +? `, + "password": "${rpcKeychainPassword}"` +: ''} + } + } +} +`, { + mode: parseInt(configFileMode, 8), + flag: 'ax' + }) + + // create an rpc key for the first user + const rpcKeychain = await loadRpcKeychain(directory) + await rpcKeychain.createKey(`rpc-user-${rpcUser}`, rpcUserKeyType) + + stdout.write(`Wrote config file to ${configFilePath}\n`) + } +} + +async function generateKey (type: string, bits: string = '2048'): Promise { + if (type === 'ed25519') { + return await createEd25519PeerId() + } else if (type === 'secp256k1') { + return await createSecp256k1PeerId() + } else if (type === 'rsa') { + return await createRSAPeerId({ + bits: parseInt(bits) + }) + } + + throw new InvalidParametersError(`Unknown key type "${type}" - must be "ed25519", "secp256k1" or "rsa"`) +} diff --git a/packages/helia-cli/src/commands/rpc/index.ts b/packages/helia-cli/src/commands/rpc/index.ts new file mode 100644 index 0000000..9dbcde1 --- /dev/null +++ b/packages/helia-cli/src/commands/rpc/index.ts @@ -0,0 +1,18 @@ +import type { Command } from '@helia/cli-utils' +import { rpcRmuser } from './rmuser.js' +import { rpcUseradd } from './useradd.js' +import { rpcUsers } from './users.js' + +export const rpc: Command = { + command: 'rpc', + description: 'Update the config of the Helia RPC server', + example: '$ helia rpc', + subcommands: [ + rpcRmuser, + rpcUseradd, + rpcUsers + ], + async execute ({ stdout }) { + stdout.write('Please enter a subcommand\n') + } +} diff --git a/packages/helia-cli/src/commands/rpc/rmuser.ts b/packages/helia-cli/src/commands/rpc/rmuser.ts new file mode 100644 index 0000000..2fdafc7 --- /dev/null +++ b/packages/helia-cli/src/commands/rpc/rmuser.ts @@ -0,0 +1,25 @@ +import type { Command } from '@helia/cli-utils' +import { loadRpcKeychain } from '@helia/cli-utils/load-rpc-keychain' + +interface AddRpcUserArgs { + positionals: string[] +} + +export const rpcRmuser: Command = { + command: 'rmuser', + description: 'Remove a RPC user from your Helia node', + example: '$ helia rpc rmuser ', + async execute ({ directory, positionals, stdout }) { + const user = positionals[0] ?? process.env.USER + + if (user == null) { + throw new Error('No user specified') + } + + const keychain = await loadRpcKeychain(directory) + + await keychain.removeKey(`rpc-user-${user}`) + + stdout.write(`Removed user ${user}\n`) + } +} diff --git a/packages/helia-cli/src/commands/rpc/useradd.ts b/packages/helia-cli/src/commands/rpc/useradd.ts new file mode 100644 index 0000000..026ea9c --- /dev/null +++ b/packages/helia-cli/src/commands/rpc/useradd.ts @@ -0,0 +1,41 @@ +import type { Command } from '@helia/cli-utils' +import type { KeyType } from '@libp2p/interface-keychain' +import { loadRpcKeychain } from '@helia/cli-utils/load-rpc-keychain' + +interface AddRpcUserArgs { + positionals: string[] + keyType: KeyType +} + +export const rpcUseradd: Command = { + command: 'useradd', + description: 'Add an RPC user to your Helia node', + example: '$ helia rpc useradd ', + options: { + keyType: { + description: 'The type of key', + type: 'string', + default: 'Ed25519', + valid: ['Ed25519', 'secp256k1'] + } + }, + async execute ({ directory, positionals, keyType, stdout }) { + const user = positionals[0] ?? process.env.USER + + if (user == null) { + throw new Error('No user specified') + } + + const keychain = await loadRpcKeychain(directory) + const keyName = `rpc-user-${user}` + const keys = await keychain.listKeys() + + if (keys.some(info => info.name === keyName)) { + throw new Error(`User "${user}" already exists`) + } + + await keychain.createKey(`rpc-user-${user}`, keyType) + + stdout.write(`Created user ${user}\n`) + } +} diff --git a/packages/helia-cli/src/commands/rpc/users.ts b/packages/helia-cli/src/commands/rpc/users.ts new file mode 100644 index 0000000..4ea1561 --- /dev/null +++ b/packages/helia-cli/src/commands/rpc/users.ts @@ -0,0 +1,18 @@ +import type { Command } from '@helia/cli-utils' +import { loadRpcKeychain } from '@helia/cli-utils/load-rpc-keychain' + +export const rpcUsers: Command = { + command: 'users', + description: 'List user accounts on the Helia RPC server', + example: '$ helia rpc users', + async execute ({ directory, stdout }) { + const keychain = await loadRpcKeychain(directory) + const keys = await keychain.listKeys() + + for (const info of keys) { + if (info.name.startsWith('rpc-user-')) { + stdout.write(`${info.name.substring('rpc-user-'.length)}\n`) + } + } + } +} diff --git a/packages/helia-cli/src/commands/status.ts b/packages/helia-cli/src/commands/status.ts new file mode 100644 index 0000000..7c40d25 --- /dev/null +++ b/packages/helia-cli/src/commands/status.ts @@ -0,0 +1,40 @@ +import type { Command, RootArgs } from '@helia/cli-utils' +import fs from 'node:fs' +import { logger } from '@libp2p/logger' +import { findOnlineHelia } from '@helia/cli-utils/find-helia' + +const log = logger('helia:cli:commands:status') + +export const status: Command = { + command: 'status', + description: 'Report the status of the Helia daemon', + example: '$ helia status', + async execute ({ directory, rpcAddress, stdout, user }) { + // socket file? + const socketFilePath = rpcAddress + + if (fs.existsSync(socketFilePath)) { + log(`Found socket file at ${socketFilePath}`) + + const { + helia, libp2p + } = await findOnlineHelia(directory, rpcAddress, user) + + if (libp2p != null) { + await libp2p.stop() + } + + if (helia == null) { + log(`Removing stale socket file at ${socketFilePath}`) + fs.rmSync(socketFilePath) + } else { + stdout.write('The daemon is running\n') + return + } + } else { + log(`Could not find socket file at ${socketFilePath}`) + } + + stdout.write('The daemon is not running\n') + } +} diff --git a/packages/helia-cli/src/index.ts b/packages/helia-cli/src/index.ts new file mode 100644 index 0000000..706210f --- /dev/null +++ b/packages/helia-cli/src/index.ts @@ -0,0 +1,18 @@ +#! /usr/bin/env node --trace-warnings +/* eslint-disable no-console */ + +import { cli } from '@helia/cli-utils' +import kleur from 'kleur' +import { commands } from './commands/index.js' + +async function main (): Promise { + const command = 'helia' + const description = `${kleur.bold('Helia')} is an ${kleur.cyan('IPFS')} implementation written in ${kleur.yellow('JavaScript')}` + + await cli(command, description, commands) +} + +main().catch(err => { + console.error(err) // eslint-disable-line no-console + process.exit(1) +}) diff --git a/packages/helia-cli/test/index.spec.ts b/packages/helia-cli/test/index.spec.ts new file mode 100644 index 0000000..e67dc48 --- /dev/null +++ b/packages/helia-cli/test/index.spec.ts @@ -0,0 +1,7 @@ +import { expect } from 'aegir/chai' + +describe('cli', () => { + it('should start a node', () => { + expect(true).to.be.ok() + }) +}) diff --git a/packages/helia-cli/tsconfig.json b/packages/helia-cli/tsconfig.json new file mode 100644 index 0000000..fda662b --- /dev/null +++ b/packages/helia-cli/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../cli-utils" + }, + { + "path": "../rpc-server" + } + ] +} diff --git a/packages/rpc-client/.aegir.js b/packages/rpc-client/.aegir.js new file mode 100644 index 0000000..e9c18f3 --- /dev/null +++ b/packages/rpc-client/.aegir.js @@ -0,0 +1,6 @@ + +export default { + build: { + bundle: false + } +} diff --git a/packages/rpc-client/LICENSE b/packages/rpc-client/LICENSE new file mode 100644 index 0000000..20ce483 --- /dev/null +++ b/packages/rpc-client/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/rpc-client/LICENSE-APACHE b/packages/rpc-client/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/packages/rpc-client/LICENSE-APACHE @@ -0,0 +1,5 @@ +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. diff --git a/packages/rpc-client/LICENSE-MIT b/packages/rpc-client/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/packages/rpc-client/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/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. diff --git a/packages/rpc-client/README.md b/packages/rpc-client/README.md new file mode 100644 index 0000000..67f7c23 --- /dev/null +++ b/packages/rpc-client/README.md @@ -0,0 +1,53 @@ +# @helia/rpc-client + +[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) +[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech) +[![codecov](https://img.shields.io/codecov/c/github/ipfs/helia-cli.svg?style=flat-square)](https://codecov.io/gh/ipfs/helia-cli) +[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/helia-cli/js-test-and-release.yml?branch=main\&style=flat-square)](https://github.com/ipfs/helia-cli/actions/workflows/js-test-and-release.yml?query=branch%3Amain) + +> An implementation of IPFS in JavaScript + +## Table of contents + +- [Install](#install) + - [Browser ` +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribute + +Contributions welcome! Please check out [the issues](https://github.com/ipfs/helia-cli/issues). + +Also see our [contributing document](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) for more information on how we work, and about contributing in general. + +Please be aware that all interactions related to this repo are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) diff --git a/packages/rpc-client/package.json b/packages/rpc-client/package.json new file mode 100644 index 0000000..36be127 --- /dev/null +++ b/packages/rpc-client/package.json @@ -0,0 +1,158 @@ +{ + "name": "@helia/rpc-client", + "version": "0.0.0", + "description": "An implementation of IPFS in JavaScript", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/ipfs/helia-cli/tree/master/packages/rpc-client#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/ipfs/helia-cli.git" + }, + "bugs": { + "url": "https://github.com/ipfs/helia-cli/issues" + }, + "keywords": [ + "IPFS" + ], + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "main" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "test": "aegir test", + "test:chrome": "aegir test -t browser --cov", + "test:chrome-webworker": "aegir test -t webworker", + "test:firefox": "aegir test -t browser -- --browser firefox", + "test:firefox-webworker": "aegir test -t webworker -- --browser firefox", + "test:node": "aegir test -t node --cov", + "test:electron-main": "aegir test -t electron-main", + "release": "aegir release" + }, + "dependencies": { + "@helia/interface": "next", + "@helia/rpc-protocol": "~0.0.0", + "@libp2p/interface-libp2p": "^1.1.0", + "@libp2p/logger": "^2.0.5", + "@libp2p/peer-id": "^2.0.0", + "@multiformats/multiaddr": "^11.1.5", + "interface-blockstore": "^4.0.1", + "it-first": "^2.0.0", + "it-pb-stream": "^2.0.3", + "multiformats": "^11.0.1" + }, + "devDependencies": { + "aegir": "^38.1.0" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/rpc-client/src/commands/authorization/get.ts b/packages/rpc-client/src/commands/authorization/get.ts new file mode 100644 index 0000000..f78fd3a --- /dev/null +++ b/packages/rpc-client/src/commands/authorization/get.ts @@ -0,0 +1,19 @@ +import { GetOptions, GetRequest, GetResponse } from '@helia/rpc-protocol/authorization' +import type { HeliaRpcClientConfig } from '../../index.js' +import { unaryCall } from '../utils/rpc-call.js' + +export function createAuthorizationGet (config: HeliaRpcClientConfig): (user: string, options?: any) => Promise { + return unaryCall({ + resource: '/authorization/get', + optionsCodec: GetOptions, + transformInput: (user) => { + return { + user + } + }, + outputCodec: GetResponse, + transformOutput: (obj) => { + return obj.authorization + } + })(config) +} diff --git a/packages/rpc-client/src/commands/blockstore/batch.ts b/packages/rpc-client/src/commands/blockstore/batch.ts new file mode 100644 index 0000000..b7a753e --- /dev/null +++ b/packages/rpc-client/src/commands/blockstore/batch.ts @@ -0,0 +1,84 @@ +import { BatchOptions, BatchRequest, BatchRequestDelete, BatchRequestPut, BatchRequestType } from '@helia/rpc-protocol/blockstore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import type { CID } from 'multiformats/cid' +import { RPCCallMessage, RPCCallRequest, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import { HELIA_RPC_PROTOCOL } from '@helia/rpc-protocol' +import { pbStream } from 'it-pb-stream' +import type { Pair, Batch } from 'interface-blockstore' + +export function createBlockstoreBatch (config: HeliaRpcMethodConfig): Helia['blockstore']['batch'] { + const batch = (): Batch => { + let puts: Pair[] = [] + let dels: CID[] = [] + + const batch: Batch = { + put (key, value) { + puts.push({ key, value }) + }, + + delete (key) { + dels.push(key) + }, + + commit: async (options) => { + const duplex = await config.libp2p.dialProtocol(config.multiaddr, HELIA_RPC_PROTOCOL) + + try { + const stream = pbStream(duplex) + + stream.writePB({ + resource: '/blockstore/batch', + method: 'INVOKE', + authorization: config.authorization, + options: BatchOptions.encode({ + ...options + }) + }, RPCCallRequest) + + for (const { key, value } of puts) { + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: BatchRequest.encode({ + type: BatchRequestType.BATCH_REQUEST_PUT, + message: BatchRequestPut.encode({ + cid: key.bytes, + block: value + }) + }) + }, RPCCallMessage) + } + + puts = [] + + for (const cid of dels) { + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: BatchRequest.encode({ + type: BatchRequestType.BATCH_REQUEST_DELETE, + message: BatchRequestDelete.encode({ + cid: cid.bytes + }) + }) + }, RPCCallMessage) + } + + dels = [] + + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: BatchRequest.encode({ + type: BatchRequestType.BATCH_REQUEST_COMMIT + }) + }, RPCCallMessage) + } finally { + duplex.close() + } + } + } + + return batch + } + + return batch +} diff --git a/packages/rpc-client/src/commands/blockstore/close.ts b/packages/rpc-client/src/commands/blockstore/close.ts new file mode 100644 index 0000000..eea558e --- /dev/null +++ b/packages/rpc-client/src/commands/blockstore/close.ts @@ -0,0 +1,11 @@ +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import { unaryCall } from '../utils/rpc-call.js' +import { CloseOptions } from '@helia/rpc-protocol/blockstore' + +export function createBlockstoreClose (config: HeliaRpcMethodConfig): Helia['blockstore']['close'] { + return unaryCall({ + resource: '/blockstore/close', + optionsCodec: CloseOptions + })(config) +} diff --git a/packages/rpc-client/src/commands/blockstore/delete-many.ts b/packages/rpc-client/src/commands/blockstore/delete-many.ts new file mode 100644 index 0000000..f790185 --- /dev/null +++ b/packages/rpc-client/src/commands/blockstore/delete-many.ts @@ -0,0 +1,22 @@ +import { DeleteManyOptions, DeleteManyRequest, DeleteManyResponse } from '@helia/rpc-protocol/blockstore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import { CID } from 'multiformats/cid' +import { streamingCall } from '../utils/rpc-call.js' + +export function createBlockstoreDeleteMany (config: HeliaRpcMethodConfig): Helia['blockstore']['deleteMany'] { + return streamingCall({ + resource: '/blockstore/delete-many', + optionsCodec: DeleteManyOptions, + transformInput: (cid: CID) => { + return { + cid: cid.bytes + } + }, + inputCodec: DeleteManyRequest, + outputCodec: DeleteManyResponse, + transformOutput: (obj: DeleteManyResponse) => { + return CID.decode(obj.cid) + } + })(config) +} diff --git a/packages/rpc-client/src/commands/blockstore/delete.ts b/packages/rpc-client/src/commands/blockstore/delete.ts new file mode 100644 index 0000000..a1316fb --- /dev/null +++ b/packages/rpc-client/src/commands/blockstore/delete.ts @@ -0,0 +1,18 @@ +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import type { CID } from 'multiformats/cid' +import { unaryCall } from '../utils/rpc-call.js' +import { DeleteOptions, DeleteRequest } from '@helia/rpc-protocol/blockstore' + +export function createBlockstoreDelete (config: HeliaRpcMethodConfig): Helia['blockstore']['delete'] { + return unaryCall({ + resource: '/blockstore/delete', + optionsCodec: DeleteOptions, + transformInput: (cid: CID) => { + return { + cid: cid.bytes + } + }, + inputCodec: DeleteRequest + })(config) +} diff --git a/packages/rpc-client/src/commands/blockstore/get-many.ts b/packages/rpc-client/src/commands/blockstore/get-many.ts new file mode 100644 index 0000000..b17659c --- /dev/null +++ b/packages/rpc-client/src/commands/blockstore/get-many.ts @@ -0,0 +1,22 @@ +import { GetManyOptions, GetManyRequest, GetManyResponse } from '@helia/rpc-protocol/blockstore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import type { CID } from 'multiformats/cid' +import { streamingCall } from '../utils/rpc-call.js' + +export function createBlockstoreGetMany (config: HeliaRpcMethodConfig): Helia['blockstore']['getMany'] { + return streamingCall({ + resource: '/blockstore/get-many', + optionsCodec: GetManyOptions, + transformInput: (cid: CID) => { + return { + cid: cid.bytes + } + }, + inputCodec: GetManyRequest, + outputCodec: GetManyResponse, + transformOutput: (obj) => { + return obj.block + } + })(config) +} diff --git a/packages/rpc-client/src/commands/blockstore/get.ts b/packages/rpc-client/src/commands/blockstore/get.ts new file mode 100644 index 0000000..94141f2 --- /dev/null +++ b/packages/rpc-client/src/commands/blockstore/get.ts @@ -0,0 +1,22 @@ +import { GetOptions, GetRequest, GetResponse } from '@helia/rpc-protocol/blockstore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import type { CID } from 'multiformats/cid' +import { unaryCall } from '../utils/rpc-call.js' + +export function createBlockstoreGet (config: HeliaRpcMethodConfig): Helia['blockstore']['get'] { + return unaryCall({ + resource: '/blockstore/get', + optionsCodec: GetOptions, + transformInput: (cid: CID) => { + return { + cid: cid.bytes + } + }, + inputCodec: GetRequest, + outputCodec: GetResponse, + transformOutput: (obj: GetResponse): Uint8Array => { + return obj.block + } + })(config) +} diff --git a/packages/rpc-client/src/commands/blockstore/has.ts b/packages/rpc-client/src/commands/blockstore/has.ts new file mode 100644 index 0000000..9f66036 --- /dev/null +++ b/packages/rpc-client/src/commands/blockstore/has.ts @@ -0,0 +1,22 @@ +import { HasOptions, HasRequest, HasResponse } from '@helia/rpc-protocol/blockstore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import type { CID } from 'multiformats/cid' +import { unaryCall } from '../utils/rpc-call.js' + +export function createBlockstoreHas (config: HeliaRpcMethodConfig): Helia['blockstore']['has'] { + return unaryCall({ + resource: '/blockstore/has', + optionsCodec: HasOptions, + transformInput: (cid: CID) => { + return { + cid: cid.bytes + } + }, + inputCodec: HasRequest, + outputCodec: HasResponse, + transformOutput: (obj) => { + return obj.has + } + })(config) +} diff --git a/packages/rpc-client/src/commands/blockstore/open.ts b/packages/rpc-client/src/commands/blockstore/open.ts new file mode 100644 index 0000000..a947303 --- /dev/null +++ b/packages/rpc-client/src/commands/blockstore/open.ts @@ -0,0 +1,11 @@ +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import { unaryCall } from '../utils/rpc-call.js' +import { OpenOptions } from '@helia/rpc-protocol/blockstore' + +export function createBlockstoreOpen (config: HeliaRpcMethodConfig): Helia['blockstore']['open'] { + return unaryCall({ + resource: '/blockstore/open', + optionsCodec: OpenOptions + })(config) +} diff --git a/packages/rpc-client/src/commands/blockstore/put-many.ts b/packages/rpc-client/src/commands/blockstore/put-many.ts new file mode 100644 index 0000000..9029c31 --- /dev/null +++ b/packages/rpc-client/src/commands/blockstore/put-many.ts @@ -0,0 +1,23 @@ +import { PutManyOptions, PutManyRequest, PutManyResponse } from '@helia/rpc-protocol/blockstore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import { streamingCall } from '../utils/rpc-call.js' +import type { Pair } from 'interface-blockstore' + +export function createBlockstorePutMany (config: HeliaRpcMethodConfig): Helia['blockstore']['putMany'] { + return streamingCall({ + resource: '/blockstore/put-many', + optionsCodec: PutManyOptions, + transformInput: (pair: Pair) => { + return { + cid: pair.key.bytes, + block: pair.value + } + }, + inputCodec: PutManyRequest, + outputCodec: PutManyResponse, + transformOutput: (obj): Uint8Array => { + return obj.block + } + })(config) +} diff --git a/packages/rpc-client/src/commands/blockstore/put.ts b/packages/rpc-client/src/commands/blockstore/put.ts new file mode 100644 index 0000000..704912a --- /dev/null +++ b/packages/rpc-client/src/commands/blockstore/put.ts @@ -0,0 +1,19 @@ +import { PutOptions, PutRequest } from '@helia/rpc-protocol/blockstore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import type { Pair } from 'interface-blockstore' +import { unaryCall } from '../utils/rpc-call.js' + +export function createBlockstorePut (config: HeliaRpcMethodConfig): Helia['blockstore']['put'] { + return unaryCall({ + resource: '/blockstore/put', + optionsCodec: PutOptions, + transformInput: (pair: Pair): PutRequest => { + return { + cid: pair.key.bytes, + block: pair.value + } + }, + inputCodec: PutRequest + })(config) +} diff --git a/packages/rpc-client/src/commands/blockstore/query-keys.ts b/packages/rpc-client/src/commands/blockstore/query-keys.ts new file mode 100644 index 0000000..1ed75c5 --- /dev/null +++ b/packages/rpc-client/src/commands/blockstore/query-keys.ts @@ -0,0 +1,16 @@ +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import { CID } from 'multiformats/cid' +import { QueryKeysOptions, QueryKeysRequest, QueryKeysResponse } from '@helia/rpc-protocol/blockstore' +import { streamingCall } from '../utils/rpc-call.js' + +export function createBlockstoreQueryKeys (config: HeliaRpcMethodConfig): Helia['blockstore']['queryKeys'] { + return streamingCall({ + resource: '/blockstore/query-keys', + optionsCodec: QueryKeysOptions, + outputCodec: QueryKeysResponse, + transformOutput: (obj: QueryKeysResponse) => { + return CID.decode(obj.key) + } + })(config) +} diff --git a/packages/rpc-client/src/commands/blockstore/query.ts b/packages/rpc-client/src/commands/blockstore/query.ts new file mode 100644 index 0000000..43277a4 --- /dev/null +++ b/packages/rpc-client/src/commands/blockstore/query.ts @@ -0,0 +1,19 @@ +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import { CID } from 'multiformats/cid' +import { QueryOptions, QueryRequest, QueryResponse } from '@helia/rpc-protocol/blockstore' +import { streamingCall } from '../utils/rpc-call.js' + +export function createBlockstoreQuery (config: HeliaRpcMethodConfig): Helia['blockstore']['query'] { + return streamingCall({ + resource: '/blockstore/query', + optionsCodec: QueryOptions, + outputCodec: QueryResponse, + transformOutput: (obj: QueryResponse) => { + return { + key: CID.decode(obj.key), + value: obj.value + } + } + })(config) +} diff --git a/packages/rpc-client/src/commands/info.ts b/packages/rpc-client/src/commands/info.ts new file mode 100644 index 0000000..54bb0e1 --- /dev/null +++ b/packages/rpc-client/src/commands/info.ts @@ -0,0 +1,27 @@ +import { multiaddr } from '@multiformats/multiaddr' +import { InfoOptions, InfoResponse } from '@helia/rpc-protocol/root' +import type { Helia } from '@helia/interface' +import { peerIdFromString } from '@libp2p/peer-id' +import type { HeliaRpcMethodConfig } from '../index.js' +import { unaryCall } from './utils/rpc-call.js' + +export function createInfo (config: HeliaRpcMethodConfig): Helia['info'] { + return unaryCall({ + resource: '/info', + optionsCodec: InfoOptions, + transformOptions: (obj) => { + return { + ...obj, + peerId: obj.peerId != null ? obj.peerId.toString() : undefined + } + }, + outputCodec: InfoResponse, + transformOutput: (obj) => { + return { + ...obj, + peerId: peerIdFromString(obj.peerId), + multiaddrs: obj.multiaddrs.map(str => multiaddr(str)) + } + } + })(config) +} diff --git a/packages/rpc-client/src/commands/utils/rpc-call.ts b/packages/rpc-client/src/commands/utils/rpc-call.ts new file mode 100644 index 0000000..5082d78 --- /dev/null +++ b/packages/rpc-client/src/commands/utils/rpc-call.ts @@ -0,0 +1,112 @@ +/* eslint max-depth: ["error", 5] */ + +import { RPCCallMessage, RPCCallRequest, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import { HELIA_RPC_PROTOCOL, RPCError, RPCProgressEvent } from '@helia/rpc-protocol' +import type { HeliaRpcMethodConfig } from '../../index.js' +import { pbStream } from 'it-pb-stream' +import first from 'it-first' +import { logger } from '@libp2p/logger' + +const log = logger('helia:rpc-client:utils:rpc-call') + +export interface Codec { + encode: (type: T) => Uint8Array + decode: (buf: Uint8Array) => T +} + +export interface CallOptions { + resource: string + optionsCodec: Codec + transformOptions?: (obj: any) => Options + transformInput?: (obj: any) => Request + inputCodec?: Codec + outputCodec?: Codec + transformOutput?: (obj: Response) => any +} + +export function streamingCall (opts: CallOptions): (config: HeliaRpcMethodConfig) => any { + return function createStreamingCall (config: HeliaRpcMethodConfig): any { + const streamingCall: any = async function * (source: any, options: any = {}) { + const duplex = await config.libp2p.dialProtocol(config.multiaddr, HELIA_RPC_PROTOCOL) + const stream = pbStream(duplex) + + stream.writePB({ + resource: opts.resource, + method: 'INVOKE', + authorization: config.authorization, + options: opts.optionsCodec.encode(opts.transformOptions == null ? options : opts.transformOptions(options)) + }, RPCCallRequest) + + void Promise.resolve().then(async () => { + for await (const input of source) { + let message: Uint8Array | undefined + + if (opts.inputCodec != null) { + message = opts.inputCodec.encode(opts.transformInput == null ? input : opts.transformInput(input)) + } + + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message + }, RPCCallMessage) + } + + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_DONE + }, RPCCallMessage) + }) + .catch(err => { + log('error encountered while sending RPC messages', err) + }) + .finally(() => { + duplex.closeWrite() + }) + + try { + while (true) { + const response = await stream.readPB(RPCCallMessage) + + switch (response.type) { + case RPCCallMessageType.RPC_CALL_DONE: + return + case RPCCallMessageType.RPC_CALL_ERROR: + throw new RPCError(response.message) + case RPCCallMessageType.RPC_CALL_MESSAGE: + if (opts.outputCodec != null) { + let message = opts.outputCodec.decode(response.message) + + if (opts.transformOutput != null) { + message = opts.transformOutput(message) + } + + yield message + } + continue + case RPCCallMessageType.RPC_CALL_PROGRESS: + if (options.progress != null) { + options.progress(new RPCProgressEvent(response.message)) + } + continue + default: + throw new Error('Unknown RPCCallMessageType') + } + } + } finally { + duplex.closeRead() + } + } + + return streamingCall + } +} + +export function unaryCall (opts: CallOptions): (config: HeliaRpcMethodConfig) => any { + return function createStreamingCall (config: HeliaRpcMethodConfig): any { + const unaryCall: any = async function (arg: any, options: any = {}): Promise { + const fn: any = streamingCall(opts)(config) + return await first(fn([arg], options)) + } + + return unaryCall + } +} diff --git a/packages/rpc-client/src/index.ts b/packages/rpc-client/src/index.ts new file mode 100644 index 0000000..8feb322 --- /dev/null +++ b/packages/rpc-client/src/index.ts @@ -0,0 +1,69 @@ +import type { Helia } from '@helia/interface' +import { createInfo } from './commands/info.js' +import type { Libp2p } from '@libp2p/interface-libp2p' +import type { Multiaddr } from '@multiformats/multiaddr' +import { createBlockstoreDelete } from './commands/blockstore/delete.js' +import { createBlockstoreGet } from './commands/blockstore/get.js' +import { createBlockstoreHas } from './commands/blockstore/has.js' +import { createBlockstorePut } from './commands/blockstore/put.js' +import { createAuthorizationGet } from './commands/authorization/get.js' +import { createBlockstoreDeleteMany } from './commands/blockstore/delete-many.js' +import { createBlockstoreGetMany } from './commands/blockstore/get-many.js' +import { createBlockstorePutMany } from './commands/blockstore/put-many.js' +import { createBlockstoreClose } from './commands/blockstore/close.js' +import { createBlockstoreOpen } from './commands/blockstore/open.js' +import { createBlockstoreBatch } from './commands/blockstore/batch.js' +import { createBlockstoreQueryKeys } from './commands/blockstore/query-keys.js' +import { createBlockstoreQuery } from './commands/blockstore/query.js' + +export interface HeliaRpcClientConfig { + multiaddr: Multiaddr + libp2p: Libp2p + user: string +} + +export interface HeliaRpcMethodConfig { + multiaddr: Multiaddr + libp2p: Libp2p + authorization?: string +} + +export async function createHeliaRpcClient (config: HeliaRpcClientConfig): Promise { + await config.libp2p.dial(config.multiaddr) + + const getAuthorization = createAuthorizationGet(config) + const authorization = await getAuthorization(config.user) + const methodConfig = { + ...config, + authorization + } + + return { + info: createInfo(methodConfig), + blockstore: { + batch: createBlockstoreBatch(methodConfig), + close: createBlockstoreClose(methodConfig), + deleteMany: createBlockstoreDeleteMany(methodConfig), + delete: createBlockstoreDelete(methodConfig), + getMany: createBlockstoreGetMany(methodConfig), + get: createBlockstoreGet(methodConfig), + has: createBlockstoreHas(methodConfig), + open: createBlockstoreOpen(methodConfig), + putMany: createBlockstorePutMany(methodConfig), + put: createBlockstorePut(methodConfig), + queryKeys: createBlockstoreQueryKeys(methodConfig), + query: createBlockstoreQuery(methodConfig) + }, + // @ts-expect-error incomplete implementation + datastore: { + + }, + // @ts-expect-error incomplete implementation + libp2p: { + + }, + async stop () { + throw new Error('Not implemented') + } + } +} diff --git a/packages/rpc-client/test/index.spec.ts b/packages/rpc-client/test/index.spec.ts new file mode 100644 index 0000000..86e6f95 --- /dev/null +++ b/packages/rpc-client/test/index.spec.ts @@ -0,0 +1,9 @@ +/* eslint-env mocha */ + +import '../src/index.js' + +describe('rpc-client', () => { + it('should work', async () => { + + }) +}) diff --git a/packages/rpc-client/tsconfig.json b/packages/rpc-client/tsconfig.json new file mode 100644 index 0000000..447a80d --- /dev/null +++ b/packages/rpc-client/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../rpc-protocol" + } + ] +} diff --git a/packages/rpc-protocol/LICENSE b/packages/rpc-protocol/LICENSE new file mode 100644 index 0000000..20ce483 --- /dev/null +++ b/packages/rpc-protocol/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/rpc-protocol/LICENSE-APACHE b/packages/rpc-protocol/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/packages/rpc-protocol/LICENSE-APACHE @@ -0,0 +1,5 @@ +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. diff --git a/packages/rpc-protocol/LICENSE-MIT b/packages/rpc-protocol/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/packages/rpc-protocol/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/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. diff --git a/packages/rpc-protocol/README.md b/packages/rpc-protocol/README.md new file mode 100644 index 0000000..08b639f --- /dev/null +++ b/packages/rpc-protocol/README.md @@ -0,0 +1,44 @@ +# @helia/rpc-protocol + +[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) +[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech) +[![codecov](https://img.shields.io/codecov/c/github/ipfs/helia-cli.svg?style=flat-square)](https://codecov.io/gh/ipfs/helia-cli) +[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/helia-cli/js-test-and-release.yml?branch=main\&style=flat-square)](https://github.com/ipfs/helia-cli/actions/workflows/js-test-and-release.yml?query=branch%3Amain) + +> RPC protocol for use by @helia/rpc-client and @helia/rpc-server + +## Table of contents + +- [Install](#install) +- [API Docs](#api-docs) +- [License](#license) +- [Contribute](#contribute) + +## Install + +```console +$ npm i @helia/rpc-protocol +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribute + +Contributions welcome! Please check out [the issues](https://github.com/ipfs/helia-cli/issues). + +Also see our [contributing document](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) for more information on how we work, and about contributing in general. + +Please be aware that all interactions related to this repo are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) diff --git a/packages/rpc-protocol/package.json b/packages/rpc-protocol/package.json new file mode 100644 index 0000000..abdb9c8 --- /dev/null +++ b/packages/rpc-protocol/package.json @@ -0,0 +1,177 @@ +{ + "name": "@helia/rpc-protocol", + "version": "0.0.0", + "description": "RPC protocol for use by @helia/rpc-client and @helia/rpc-server", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/ipfs/helia-cli/tree/master/packages/rpc-protocol#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/ipfs/helia-cli.git" + }, + "bugs": { + "url": "https://github.com/ipfs/helia-cli/issues" + }, + "keywords": [ + "IPFS" + ], + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "type": "module", + "types": "./dist/src/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ], + "src/*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ] + } + }, + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + }, + "./authorization": { + "types": "./dist/src/authorization.d.ts", + "import": "./dist/src/authorization.js" + }, + "./blockstore": { + "types": "./dist/src/blockstore.d.ts", + "import": "./dist/src/blockstore.js" + }, + "./root": { + "types": "./dist/src/root.d.ts", + "import": "./dist/src/root.js" + }, + "./rpc": { + "types": "./dist/src/rpc.d.ts", + "import": "./dist/src/rpc.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "main" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check -i protons", + "build": "aegir build", + "release": "aegir release", + "generate": "protons src/*.proto" + }, + "dependencies": { + "protons-runtime": "^5.0.0", + "uint8arraylist": "^2.4.3" + }, + "devDependencies": { + "aegir": "^38.1.0", + "protons": "^7.0.0" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/rpc-protocol/src/authorization.proto b/packages/rpc-protocol/src/authorization.proto new file mode 100644 index 0000000..9815f4b --- /dev/null +++ b/packages/rpc-protocol/src/authorization.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +message GetOptions { + +} + +message GetRequest { + string user = 1; +} + +message GetResponse { + string authorization = 1; +} diff --git a/packages/rpc-protocol/src/authorization.ts b/packages/rpc-protocol/src/authorization.ts new file mode 100644 index 0000000..2f839d6 --- /dev/null +++ b/packages/rpc-protocol/src/authorization.ts @@ -0,0 +1,171 @@ +/* eslint-disable import/export */ +/* eslint-disable complexity */ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import { encodeMessage, decodeMessage, message } from 'protons-runtime' +import type { Codec } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' + +export interface GetOptions {} + +export namespace GetOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetOptions => { + return decodeMessage(buf, GetOptions.codec()) + } +} + +export interface GetRequest { + user: string +} + +export namespace GetRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.user != null && obj.user !== '')) { + w.uint32(10) + w.string(obj.user ?? '') + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + user: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.user = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetRequest => { + return decodeMessage(buf, GetRequest.codec()) + } +} + +export interface GetResponse { + authorization: string +} + +export namespace GetResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.authorization != null && obj.authorization !== '')) { + w.uint32(10) + w.string(obj.authorization ?? '') + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + authorization: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.authorization = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetResponse => { + return decodeMessage(buf, GetResponse.codec()) + } +} diff --git a/packages/rpc-protocol/src/blockstore.proto b/packages/rpc-protocol/src/blockstore.proto new file mode 100644 index 0000000..0ed107b --- /dev/null +++ b/packages/rpc-protocol/src/blockstore.proto @@ -0,0 +1,170 @@ +syntax = "proto3"; + +message Pair { + bytes cid = 1; + bytes block = 2; +} + +message OpenOptions { + +} + +message OpenRequest { + +} + +message OpenResponse { + +} + +message CloseOptions { + +} + +message CloseRequest { + +} + +message CloseResponse { + +} + +message PutOptions { + +} + +message PutRequest { + bytes cid = 1; + bytes block = 2; +} + +message PutResponse { + +} + +message GetOptions { + +} + +message GetRequest { + bytes cid = 1; +} + +message GetResponse { + bytes block = 2; +} + +message HasOptions { + +} + +message HasRequest { + bytes cid = 1; +} + +message HasResponse { + bool has = 1; +} + +message DeleteOptions { + +} + +message DeleteRequest { + bytes cid = 1; +} + +message DeleteResponse { + +} + +message PutManyOptions { + +} + +message PutManyRequest { + bytes cid = 1; + bytes block = 2; +} + +message PutManyResponse { + bytes cid = 1; + bytes block = 2; +} + +message GetManyOptions { + +} + +message GetManyRequest { + bytes cid = 1; +} + +message GetManyResponse { + bytes block = 1; +} + +message DeleteManyOptions { + +} + +message DeleteManyRequest { + bytes cid = 1; +} + +message DeleteManyResponse { + bytes cid = 1; +} + +message BatchOptions { + +} + +enum BatchRequestType { + BATCH_REQUEST_PUT = 0; + BATCH_REQUEST_DELETE = 1; + BATCH_REQUEST_COMMIT = 2; +} + +message BatchRequest { + BatchRequestType type = 1; + bytes message = 2; +} + +message BatchRequestPut { + bytes cid = 1; + bytes block = 2; +} + +message BatchRequestDelete { + bytes cid = 1; +} + +message BatchResponse { + +} + +message QueryOptions { + +} + +message QueryRequest { + +} + +message QueryResponse { + bytes key = 1; + bytes value = 2; +} + +message QueryKeysOptions { + +} + +message QueryKeysRequest { + +} + +message QueryKeysResponse { + bytes key = 1; +} diff --git a/packages/rpc-protocol/src/blockstore.ts b/packages/rpc-protocol/src/blockstore.ts new file mode 100644 index 0000000..7b27753 --- /dev/null +++ b/packages/rpc-protocol/src/blockstore.ts @@ -0,0 +1,2106 @@ +/* eslint-disable import/export */ +/* eslint-disable complexity */ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime' +import type { Codec } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' + +export interface Pair { + cid: Uint8Array + block: Uint8Array +} + +export namespace Pair { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid ?? new Uint8Array(0)) + } + + if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.block ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0), + block: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + case 2: + obj.block = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, Pair.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): Pair => { + return decodeMessage(buf, Pair.codec()) + } +} + +export interface OpenOptions {} + +export namespace OpenOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, OpenOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): OpenOptions => { + return decodeMessage(buf, OpenOptions.codec()) + } +} + +export interface OpenRequest {} + +export namespace OpenRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, OpenRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): OpenRequest => { + return decodeMessage(buf, OpenRequest.codec()) + } +} + +export interface OpenResponse {} + +export namespace OpenResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, OpenResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): OpenResponse => { + return decodeMessage(buf, OpenResponse.codec()) + } +} + +export interface CloseOptions {} + +export namespace CloseOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, CloseOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): CloseOptions => { + return decodeMessage(buf, CloseOptions.codec()) + } +} + +export interface CloseRequest {} + +export namespace CloseRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, CloseRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): CloseRequest => { + return decodeMessage(buf, CloseRequest.codec()) + } +} + +export interface CloseResponse {} + +export namespace CloseResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, CloseResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): CloseResponse => { + return decodeMessage(buf, CloseResponse.codec()) + } +} + +export interface PutOptions {} + +export namespace PutOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PutOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PutOptions => { + return decodeMessage(buf, PutOptions.codec()) + } +} + +export interface PutRequest { + cid: Uint8Array + block: Uint8Array +} + +export namespace PutRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid ?? new Uint8Array(0)) + } + + if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.block ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0), + block: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + case 2: + obj.block = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PutRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PutRequest => { + return decodeMessage(buf, PutRequest.codec()) + } +} + +export interface PutResponse {} + +export namespace PutResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PutResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PutResponse => { + return decodeMessage(buf, PutResponse.codec()) + } +} + +export interface GetOptions {} + +export namespace GetOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetOptions => { + return decodeMessage(buf, GetOptions.codec()) + } +} + +export interface GetRequest { + cid: Uint8Array +} + +export namespace GetRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetRequest => { + return decodeMessage(buf, GetRequest.codec()) + } +} + +export interface GetResponse { + block: Uint8Array +} + +export namespace GetResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.block ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + block: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 2: + obj.block = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetResponse => { + return decodeMessage(buf, GetResponse.codec()) + } +} + +export interface HasOptions {} + +export namespace HasOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, HasOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): HasOptions => { + return decodeMessage(buf, HasOptions.codec()) + } +} + +export interface HasRequest { + cid: Uint8Array +} + +export namespace HasRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, HasRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): HasRequest => { + return decodeMessage(buf, HasRequest.codec()) + } +} + +export interface HasResponse { + has: boolean +} + +export namespace HasResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.has != null && obj.has !== false)) { + w.uint32(8) + w.bool(obj.has ?? false) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + has: false + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.has = reader.bool() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, HasResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): HasResponse => { + return decodeMessage(buf, HasResponse.codec()) + } +} + +export interface DeleteOptions {} + +export namespace DeleteOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteOptions => { + return decodeMessage(buf, DeleteOptions.codec()) + } +} + +export interface DeleteRequest { + cid: Uint8Array +} + +export namespace DeleteRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteRequest => { + return decodeMessage(buf, DeleteRequest.codec()) + } +} + +export interface DeleteResponse {} + +export namespace DeleteResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteResponse => { + return decodeMessage(buf, DeleteResponse.codec()) + } +} + +export interface PutManyOptions {} + +export namespace PutManyOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PutManyOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PutManyOptions => { + return decodeMessage(buf, PutManyOptions.codec()) + } +} + +export interface PutManyRequest { + cid: Uint8Array + block: Uint8Array +} + +export namespace PutManyRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid ?? new Uint8Array(0)) + } + + if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.block ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0), + block: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + case 2: + obj.block = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PutManyRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PutManyRequest => { + return decodeMessage(buf, PutManyRequest.codec()) + } +} + +export interface PutManyResponse { + cid: Uint8Array + block: Uint8Array +} + +export namespace PutManyResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid ?? new Uint8Array(0)) + } + + if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.block ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0), + block: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + case 2: + obj.block = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PutManyResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PutManyResponse => { + return decodeMessage(buf, PutManyResponse.codec()) + } +} + +export interface GetManyOptions {} + +export namespace GetManyOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetManyOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetManyOptions => { + return decodeMessage(buf, GetManyOptions.codec()) + } +} + +export interface GetManyRequest { + cid: Uint8Array +} + +export namespace GetManyRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetManyRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetManyRequest => { + return decodeMessage(buf, GetManyRequest.codec()) + } +} + +export interface GetManyResponse { + block: Uint8Array +} + +export namespace GetManyResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.block ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + block: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.block = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetManyResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetManyResponse => { + return decodeMessage(buf, GetManyResponse.codec()) + } +} + +export interface DeleteManyOptions {} + +export namespace DeleteManyOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteManyOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteManyOptions => { + return decodeMessage(buf, DeleteManyOptions.codec()) + } +} + +export interface DeleteManyRequest { + cid: Uint8Array +} + +export namespace DeleteManyRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteManyRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteManyRequest => { + return decodeMessage(buf, DeleteManyRequest.codec()) + } +} + +export interface DeleteManyResponse { + cid: Uint8Array +} + +export namespace DeleteManyResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteManyResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteManyResponse => { + return decodeMessage(buf, DeleteManyResponse.codec()) + } +} + +export interface BatchOptions {} + +export namespace BatchOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, BatchOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): BatchOptions => { + return decodeMessage(buf, BatchOptions.codec()) + } +} + +export enum BatchRequestType { + BATCH_REQUEST_PUT = 'BATCH_REQUEST_PUT', + BATCH_REQUEST_DELETE = 'BATCH_REQUEST_DELETE', + BATCH_REQUEST_COMMIT = 'BATCH_REQUEST_COMMIT' +} + +enum __BatchRequestTypeValues { + BATCH_REQUEST_PUT = 0, + BATCH_REQUEST_DELETE = 1, + BATCH_REQUEST_COMMIT = 2 +} + +export namespace BatchRequestType { + export const codec = (): Codec => { + return enumeration(__BatchRequestTypeValues) + } +} +export interface BatchRequest { + type: BatchRequestType + message: Uint8Array +} + +export namespace BatchRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.type != null && __BatchRequestTypeValues[obj.type] !== 0)) { + w.uint32(8) + BatchRequestType.codec().encode(obj.type ?? BatchRequestType.BATCH_REQUEST_PUT, w) + } + + if (opts.writeDefaults === true || (obj.message != null && obj.message.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.message ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + type: BatchRequestType.BATCH_REQUEST_PUT, + message: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.type = BatchRequestType.codec().decode(reader) + break + case 2: + obj.message = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, BatchRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): BatchRequest => { + return decodeMessage(buf, BatchRequest.codec()) + } +} + +export interface BatchRequestPut { + cid: Uint8Array + block: Uint8Array +} + +export namespace BatchRequestPut { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid ?? new Uint8Array(0)) + } + + if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.block ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0), + block: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + case 2: + obj.block = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, BatchRequestPut.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): BatchRequestPut => { + return decodeMessage(buf, BatchRequestPut.codec()) + } +} + +export interface BatchRequestDelete { + cid: Uint8Array +} + +export namespace BatchRequestDelete { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, BatchRequestDelete.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): BatchRequestDelete => { + return decodeMessage(buf, BatchRequestDelete.codec()) + } +} + +export interface BatchResponse {} + +export namespace BatchResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, BatchResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): BatchResponse => { + return decodeMessage(buf, BatchResponse.codec()) + } +} + +export interface QueryOptions {} + +export namespace QueryOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, QueryOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): QueryOptions => { + return decodeMessage(buf, QueryOptions.codec()) + } +} + +export interface QueryRequest {} + +export namespace QueryRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, QueryRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): QueryRequest => { + return decodeMessage(buf, QueryRequest.codec()) + } +} + +export interface QueryResponse { + key: Uint8Array + value: Uint8Array +} + +export namespace QueryResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.key ?? new Uint8Array(0)) + } + + if (opts.writeDefaults === true || (obj.value != null && obj.value.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.value ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: new Uint8Array(0), + value: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.bytes() + break + case 2: + obj.value = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, QueryResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): QueryResponse => { + return decodeMessage(buf, QueryResponse.codec()) + } +} + +export interface QueryKeysOptions {} + +export namespace QueryKeysOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, QueryKeysOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): QueryKeysOptions => { + return decodeMessage(buf, QueryKeysOptions.codec()) + } +} + +export interface QueryKeysRequest {} + +export namespace QueryKeysRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, QueryKeysRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): QueryKeysRequest => { + return decodeMessage(buf, QueryKeysRequest.codec()) + } +} + +export interface QueryKeysResponse { + key: Uint8Array +} + +export namespace QueryKeysResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.key ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, QueryKeysResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): QueryKeysResponse => { + return decodeMessage(buf, QueryKeysResponse.codec()) + } +} diff --git a/packages/rpc-protocol/src/datastore.proto b/packages/rpc-protocol/src/datastore.proto new file mode 100644 index 0000000..6f9c370 --- /dev/null +++ b/packages/rpc-protocol/src/datastore.proto @@ -0,0 +1,164 @@ +syntax = "proto3"; + +message OpenOptions { + +} + +message OpenRequest { + +} + +message OpenResponse { + +} + +message CloseOptions { + +} + +message CloseRequest { + +} + +message CloseResponse { + +} + +message PutOptions { + +} + +message PutRequest { + string key = 1; + bytes value = 2; +} + +message PutResponse { + +} + +message GetOptions { + +} + +message GetRequest { + string key = 1; +} + +enum GetResponseType { + GET_PROGRESS = 0; + GET_RESULT = 1; +} + +message GetResponse { + GetResponseType type = 1; + optional bytes value = 2; + optional string progress_event_type = 3; + map progress_event_data = 4; +} + +message HasOptions { + +} + +message HasRequest { + string key = 1; +} + +message HasResponse { + bool has = 1; +} + +message DeleteOptions { + +} + +message DeleteRequest { + string key = 1; +} + +message DeleteResponse { + +} + +message PutManyOptions { + +} + +message PutManyRequest { + string key = 1; + bytes value = 2; +} + +message PutManyResponse { + string key = 1; + bytes value = 2; +} + +message GetManyOptions { + +} + +message GetManyRequest { + string key = 1; +} + +enum GetManyResponseType { + GET_MANY_PROGRESS = 0; + GET_MANY_RESULT = 1; +} + +message GetManyResponse { + GetManyResponseType type = 1; + optional bytes value = 2; + optional string progress_event_type = 3; + map progress_event_data = 4; +} + +message DeleteManyOptions { + +} + +message DeleteManyRequest { + string key = 1; +} + +message DeleteManyResponse { + string key = 1; +} + +message BatchOptions { + +} + +message BatchRequest { + +} + +message BatchResponse { + string id = 1; +} + +message QueryOptions { + +} + +message QueryRequest { + +} + +message QueryResponse { + +} + +message QueryKeysOptions { + +} + +message QueryKeysRequest { + +} + +message QueryKeysResponse { + +} diff --git a/packages/rpc-protocol/src/datastore.ts b/packages/rpc-protocol/src/datastore.ts new file mode 100644 index 0000000..46ae8d8 --- /dev/null +++ b/packages/rpc-protocol/src/datastore.ts @@ -0,0 +1,2085 @@ +/* eslint-disable import/export */ +/* eslint-disable complexity */ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime' +import type { Codec } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' + +export interface OpenOptions {} + +export namespace OpenOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, OpenOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): OpenOptions => { + return decodeMessage(buf, OpenOptions.codec()) + } +} + +export interface OpenRequest {} + +export namespace OpenRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, OpenRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): OpenRequest => { + return decodeMessage(buf, OpenRequest.codec()) + } +} + +export interface OpenResponse {} + +export namespace OpenResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, OpenResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): OpenResponse => { + return decodeMessage(buf, OpenResponse.codec()) + } +} + +export interface CloseOptions {} + +export namespace CloseOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, CloseOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): CloseOptions => { + return decodeMessage(buf, CloseOptions.codec()) + } +} + +export interface CloseRequest {} + +export namespace CloseRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, CloseRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): CloseRequest => { + return decodeMessage(buf, CloseRequest.codec()) + } +} + +export interface CloseResponse {} + +export namespace CloseResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, CloseResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): CloseResponse => { + return decodeMessage(buf, CloseResponse.codec()) + } +} + +export interface PutOptions {} + +export namespace PutOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PutOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PutOptions => { + return decodeMessage(buf, PutOptions.codec()) + } +} + +export interface PutRequest { + key: string + value: Uint8Array +} + +export namespace PutRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key ?? '') + } + + if (opts.writeDefaults === true || (obj.value != null && obj.value.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.value ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '', + value: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + case 2: + obj.value = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PutRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PutRequest => { + return decodeMessage(buf, PutRequest.codec()) + } +} + +export interface PutResponse {} + +export namespace PutResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PutResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PutResponse => { + return decodeMessage(buf, PutResponse.codec()) + } +} + +export interface GetOptions {} + +export namespace GetOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetOptions => { + return decodeMessage(buf, GetOptions.codec()) + } +} + +export interface GetRequest { + key: string +} + +export namespace GetRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key ?? '') + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetRequest => { + return decodeMessage(buf, GetRequest.codec()) + } +} + +export enum GetResponseType { + GET_PROGRESS = 'GET_PROGRESS', + GET_RESULT = 'GET_RESULT' +} + +enum __GetResponseTypeValues { + GET_PROGRESS = 0, + GET_RESULT = 1 +} + +export namespace GetResponseType { + export const codec = (): Codec => { + return enumeration(__GetResponseTypeValues) + } +} +export interface GetResponse { + type: GetResponseType + value?: Uint8Array + progressEventType?: string + progressEventData: Map +} + +export namespace GetResponse { + export interface GetResponse$progressEventDataEntry { + key: string + value: string + } + + export namespace GetResponse$progressEventDataEntry { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key ?? '') + } + + if (opts.writeDefaults === true || (obj.value != null && obj.value !== '')) { + w.uint32(18) + w.string(obj.value ?? '') + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '', + value: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + case 2: + obj.value = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetResponse$progressEventDataEntry.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetResponse$progressEventDataEntry => { + return decodeMessage(buf, GetResponse$progressEventDataEntry.codec()) + } + } + + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.type != null && __GetResponseTypeValues[obj.type] !== 0)) { + w.uint32(8) + GetResponseType.codec().encode(obj.type ?? GetResponseType.GET_PROGRESS, w) + } + + if (obj.value != null) { + w.uint32(18) + w.bytes(obj.value) + } + + if (obj.progressEventType != null) { + w.uint32(26) + w.string(obj.progressEventType) + } + + if (obj.progressEventData != null && obj.progressEventData.size !== 0) { + for (const [key, value] of obj.progressEventData.entries()) { + w.uint32(34) + GetResponse.GetResponse$progressEventDataEntry.codec().encode({ key, value }, w, { + writeDefaults: true + }) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + type: GetResponseType.GET_PROGRESS, + progressEventData: new Map() + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.type = GetResponseType.codec().decode(reader) + break + case 2: + obj.value = reader.bytes() + break + case 3: + obj.progressEventType = reader.string() + break + case 4: { + const entry = GetResponse.GetResponse$progressEventDataEntry.codec().decode(reader, reader.uint32()) + obj.progressEventData.set(entry.key, entry.value) + break + } + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetResponse => { + return decodeMessage(buf, GetResponse.codec()) + } +} + +export interface HasOptions {} + +export namespace HasOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, HasOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): HasOptions => { + return decodeMessage(buf, HasOptions.codec()) + } +} + +export interface HasRequest { + key: string +} + +export namespace HasRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key ?? '') + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, HasRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): HasRequest => { + return decodeMessage(buf, HasRequest.codec()) + } +} + +export interface HasResponse { + has: boolean +} + +export namespace HasResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.has != null && obj.has !== false)) { + w.uint32(8) + w.bool(obj.has ?? false) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + has: false + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.has = reader.bool() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, HasResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): HasResponse => { + return decodeMessage(buf, HasResponse.codec()) + } +} + +export interface DeleteOptions {} + +export namespace DeleteOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteOptions => { + return decodeMessage(buf, DeleteOptions.codec()) + } +} + +export interface DeleteRequest { + key: string +} + +export namespace DeleteRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key ?? '') + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteRequest => { + return decodeMessage(buf, DeleteRequest.codec()) + } +} + +export interface DeleteResponse {} + +export namespace DeleteResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteResponse => { + return decodeMessage(buf, DeleteResponse.codec()) + } +} + +export interface PutManyOptions {} + +export namespace PutManyOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PutManyOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PutManyOptions => { + return decodeMessage(buf, PutManyOptions.codec()) + } +} + +export interface PutManyRequest { + key: string + value: Uint8Array +} + +export namespace PutManyRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key ?? '') + } + + if (opts.writeDefaults === true || (obj.value != null && obj.value.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.value ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '', + value: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + case 2: + obj.value = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PutManyRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PutManyRequest => { + return decodeMessage(buf, PutManyRequest.codec()) + } +} + +export interface PutManyResponse { + key: string + value: Uint8Array +} + +export namespace PutManyResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key ?? '') + } + + if (opts.writeDefaults === true || (obj.value != null && obj.value.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.value ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '', + value: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + case 2: + obj.value = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PutManyResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PutManyResponse => { + return decodeMessage(buf, PutManyResponse.codec()) + } +} + +export interface GetManyOptions {} + +export namespace GetManyOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetManyOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetManyOptions => { + return decodeMessage(buf, GetManyOptions.codec()) + } +} + +export interface GetManyRequest { + key: string +} + +export namespace GetManyRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key ?? '') + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetManyRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetManyRequest => { + return decodeMessage(buf, GetManyRequest.codec()) + } +} + +export enum GetManyResponseType { + GET_MANY_PROGRESS = 'GET_MANY_PROGRESS', + GET_MANY_RESULT = 'GET_MANY_RESULT' +} + +enum __GetManyResponseTypeValues { + GET_MANY_PROGRESS = 0, + GET_MANY_RESULT = 1 +} + +export namespace GetManyResponseType { + export const codec = (): Codec => { + return enumeration(__GetManyResponseTypeValues) + } +} +export interface GetManyResponse { + type: GetManyResponseType + value?: Uint8Array + progressEventType?: string + progressEventData: Map +} + +export namespace GetManyResponse { + export interface GetManyResponse$progressEventDataEntry { + key: string + value: string + } + + export namespace GetManyResponse$progressEventDataEntry { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key ?? '') + } + + if (opts.writeDefaults === true || (obj.value != null && obj.value !== '')) { + w.uint32(18) + w.string(obj.value ?? '') + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '', + value: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + case 2: + obj.value = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetManyResponse$progressEventDataEntry.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetManyResponse$progressEventDataEntry => { + return decodeMessage(buf, GetManyResponse$progressEventDataEntry.codec()) + } + } + + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.type != null && __GetManyResponseTypeValues[obj.type] !== 0)) { + w.uint32(8) + GetManyResponseType.codec().encode(obj.type ?? GetManyResponseType.GET_MANY_PROGRESS, w) + } + + if (obj.value != null) { + w.uint32(18) + w.bytes(obj.value) + } + + if (obj.progressEventType != null) { + w.uint32(26) + w.string(obj.progressEventType) + } + + if (obj.progressEventData != null && obj.progressEventData.size !== 0) { + for (const [key, value] of obj.progressEventData.entries()) { + w.uint32(34) + GetManyResponse.GetManyResponse$progressEventDataEntry.codec().encode({ key, value }, w, { + writeDefaults: true + }) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + type: GetManyResponseType.GET_MANY_PROGRESS, + progressEventData: new Map() + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.type = GetManyResponseType.codec().decode(reader) + break + case 2: + obj.value = reader.bytes() + break + case 3: + obj.progressEventType = reader.string() + break + case 4: { + const entry = GetManyResponse.GetManyResponse$progressEventDataEntry.codec().decode(reader, reader.uint32()) + obj.progressEventData.set(entry.key, entry.value) + break + } + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetManyResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GetManyResponse => { + return decodeMessage(buf, GetManyResponse.codec()) + } +} + +export interface DeleteManyOptions {} + +export namespace DeleteManyOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteManyOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteManyOptions => { + return decodeMessage(buf, DeleteManyOptions.codec()) + } +} + +export interface DeleteManyRequest { + key: string +} + +export namespace DeleteManyRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key ?? '') + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteManyRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteManyRequest => { + return decodeMessage(buf, DeleteManyRequest.codec()) + } +} + +export interface DeleteManyResponse { + key: string +} + +export namespace DeleteManyResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key ?? '') + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteManyResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteManyResponse => { + return decodeMessage(buf, DeleteManyResponse.codec()) + } +} + +export interface BatchOptions {} + +export namespace BatchOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, BatchOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): BatchOptions => { + return decodeMessage(buf, BatchOptions.codec()) + } +} + +export interface BatchRequest {} + +export namespace BatchRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, BatchRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): BatchRequest => { + return decodeMessage(buf, BatchRequest.codec()) + } +} + +export interface BatchResponse { + id: string +} + +export namespace BatchResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.id != null && obj.id !== '')) { + w.uint32(10) + w.string(obj.id ?? '') + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + id: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.id = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, BatchResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): BatchResponse => { + return decodeMessage(buf, BatchResponse.codec()) + } +} + +export interface QueryOptions {} + +export namespace QueryOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, QueryOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): QueryOptions => { + return decodeMessage(buf, QueryOptions.codec()) + } +} + +export interface QueryRequest {} + +export namespace QueryRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, QueryRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): QueryRequest => { + return decodeMessage(buf, QueryRequest.codec()) + } +} + +export interface QueryResponse {} + +export namespace QueryResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, QueryResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): QueryResponse => { + return decodeMessage(buf, QueryResponse.codec()) + } +} + +export interface QueryKeysOptions {} + +export namespace QueryKeysOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, QueryKeysOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): QueryKeysOptions => { + return decodeMessage(buf, QueryKeysOptions.codec()) + } +} + +export interface QueryKeysRequest {} + +export namespace QueryKeysRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, QueryKeysRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): QueryKeysRequest => { + return decodeMessage(buf, QueryKeysRequest.codec()) + } +} + +export interface QueryKeysResponse {} + +export namespace QueryKeysResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, QueryKeysResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): QueryKeysResponse => { + return decodeMessage(buf, QueryKeysResponse.codec()) + } +} diff --git a/packages/rpc-protocol/src/index.ts b/packages/rpc-protocol/src/index.ts new file mode 100644 index 0000000..23541f4 --- /dev/null +++ b/packages/rpc-protocol/src/index.ts @@ -0,0 +1,31 @@ +import { RPCCallError, RPCCallProgress } from './rpc.js' + +export const HELIA_RPC_PROTOCOL = '/helia-rpc/0.0.1' + +export class RPCError extends Error { + public readonly name: string + public readonly code: string + + constructor (buf: Uint8Array) { + const message = RPCCallError.decode(buf) + + super(message.message ?? 'RPC error') + + this.name = message.name ?? 'RPCError' + this.code = message.code ?? 'ERR_RPC_ERROR' + this.stack = message.stack ?? this.stack + } +} + +export class RPCProgressEvent extends Event { + constructor (buf: Uint8Array) { + const event = RPCCallProgress.decode(buf) + + super(event.event ?? 'ProgressEvent') + + for (const [key, value] of event.data) { + // @ts-expect-error cannot use strings to index this type + this[key] = value + } + } +} diff --git a/packages/rpc-protocol/src/root.proto b/packages/rpc-protocol/src/root.proto new file mode 100644 index 0000000..31fbecd --- /dev/null +++ b/packages/rpc-protocol/src/root.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +message InfoOptions { + optional string peer_id = 1; +} + +message InfoResponse { + string peer_id = 1; + repeated string multiaddrs = 2; + string agent_version = 3; + string protocol_version = 4; + repeated string protocols = 5; +} diff --git a/packages/rpc-protocol/src/root.ts b/packages/rpc-protocol/src/root.ts new file mode 100644 index 0000000..40b24ef --- /dev/null +++ b/packages/rpc-protocol/src/root.ts @@ -0,0 +1,167 @@ +/* eslint-disable import/export */ +/* eslint-disable complexity */ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import { encodeMessage, decodeMessage, message } from 'protons-runtime' +import type { Codec } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' + +export interface InfoOptions { + peerId?: string +} + +export namespace InfoOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.peerId != null) { + w.uint32(10) + w.string(obj.peerId) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.peerId = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, InfoOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): InfoOptions => { + return decodeMessage(buf, InfoOptions.codec()) + } +} + +export interface InfoResponse { + peerId: string + multiaddrs: string[] + agentVersion: string + protocolVersion: string + protocols: string[] +} + +export namespace InfoResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.peerId != null && obj.peerId !== '')) { + w.uint32(10) + w.string(obj.peerId ?? '') + } + + if (obj.multiaddrs != null) { + for (const value of obj.multiaddrs) { + w.uint32(18) + w.string(value) + } + } + + if (opts.writeDefaults === true || (obj.agentVersion != null && obj.agentVersion !== '')) { + w.uint32(26) + w.string(obj.agentVersion ?? '') + } + + if (opts.writeDefaults === true || (obj.protocolVersion != null && obj.protocolVersion !== '')) { + w.uint32(34) + w.string(obj.protocolVersion ?? '') + } + + if (obj.protocols != null) { + for (const value of obj.protocols) { + w.uint32(42) + w.string(value) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + peerId: '', + multiaddrs: [], + agentVersion: '', + protocolVersion: '', + protocols: [] + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.peerId = reader.string() + break + case 2: + obj.multiaddrs.push(reader.string()) + break + case 3: + obj.agentVersion = reader.string() + break + case 4: + obj.protocolVersion = reader.string() + break + case 5: + obj.protocols.push(reader.string()) + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, InfoResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): InfoResponse => { + return decodeMessage(buf, InfoResponse.codec()) + } +} diff --git a/packages/rpc-protocol/src/rpc.proto b/packages/rpc-protocol/src/rpc.proto new file mode 100644 index 0000000..177b215 --- /dev/null +++ b/packages/rpc-protocol/src/rpc.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +message RPCCallRequest { + string resource = 1; + string method = 2; + string authorization = 3; + bytes options = 4; +} + +enum RPCCallMessageType { + RPC_CALL_DONE = 0; + RPC_CALL_ERROR = 1; + RPC_CALL_MESSAGE = 2; + RPC_CALL_PROGRESS = 3; +} + +message RPCCallMessage { + RPCCallMessageType type = 1; + bytes message = 2; +} + +message RPCCallError { + optional string name = 1; + optional string message = 2; + optional string stack = 3; + optional string code = 4; +} + +message RPCCallProgress { + string event = 1; + map data = 4; +} diff --git a/packages/rpc-protocol/src/rpc.ts b/packages/rpc-protocol/src/rpc.ts new file mode 100644 index 0000000..659c712 --- /dev/null +++ b/packages/rpc-protocol/src/rpc.ts @@ -0,0 +1,409 @@ +/* eslint-disable import/export */ +/* eslint-disable complexity */ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime' +import type { Codec } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' + +export interface RPCCallRequest { + resource: string + method: string + authorization: string + options: Uint8Array +} + +export namespace RPCCallRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.resource != null && obj.resource !== '')) { + w.uint32(10) + w.string(obj.resource ?? '') + } + + if (opts.writeDefaults === true || (obj.method != null && obj.method !== '')) { + w.uint32(18) + w.string(obj.method ?? '') + } + + if (opts.writeDefaults === true || (obj.authorization != null && obj.authorization !== '')) { + w.uint32(26) + w.string(obj.authorization ?? '') + } + + if (opts.writeDefaults === true || (obj.options != null && obj.options.byteLength > 0)) { + w.uint32(34) + w.bytes(obj.options ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + resource: '', + method: '', + authorization: '', + options: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.resource = reader.string() + break + case 2: + obj.method = reader.string() + break + case 3: + obj.authorization = reader.string() + break + case 4: + obj.options = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, RPCCallRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): RPCCallRequest => { + return decodeMessage(buf, RPCCallRequest.codec()) + } +} + +export enum RPCCallMessageType { + RPC_CALL_DONE = 'RPC_CALL_DONE', + RPC_CALL_ERROR = 'RPC_CALL_ERROR', + RPC_CALL_MESSAGE = 'RPC_CALL_MESSAGE', + RPC_CALL_PROGRESS = 'RPC_CALL_PROGRESS' +} + +enum __RPCCallMessageTypeValues { + RPC_CALL_DONE = 0, + RPC_CALL_ERROR = 1, + RPC_CALL_MESSAGE = 2, + RPC_CALL_PROGRESS = 3 +} + +export namespace RPCCallMessageType { + export const codec = (): Codec => { + return enumeration(__RPCCallMessageTypeValues) + } +} +export interface RPCCallMessage { + type: RPCCallMessageType + message: Uint8Array +} + +export namespace RPCCallMessage { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.type != null && __RPCCallMessageTypeValues[obj.type] !== 0)) { + w.uint32(8) + RPCCallMessageType.codec().encode(obj.type ?? RPCCallMessageType.RPC_CALL_DONE, w) + } + + if (opts.writeDefaults === true || (obj.message != null && obj.message.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.message ?? new Uint8Array(0)) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + type: RPCCallMessageType.RPC_CALL_DONE, + message: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.type = RPCCallMessageType.codec().decode(reader) + break + case 2: + obj.message = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, RPCCallMessage.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): RPCCallMessage => { + return decodeMessage(buf, RPCCallMessage.codec()) + } +} + +export interface RPCCallError { + name?: string + message?: string + stack?: string + code?: string +} + +export namespace RPCCallError { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.name != null) { + w.uint32(10) + w.string(obj.name) + } + + if (obj.message != null) { + w.uint32(18) + w.string(obj.message) + } + + if (obj.stack != null) { + w.uint32(26) + w.string(obj.stack) + } + + if (obj.code != null) { + w.uint32(34) + w.string(obj.code) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.name = reader.string() + break + case 2: + obj.message = reader.string() + break + case 3: + obj.stack = reader.string() + break + case 4: + obj.code = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, RPCCallError.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): RPCCallError => { + return decodeMessage(buf, RPCCallError.codec()) + } +} + +export interface RPCCallProgress { + event: string + data: Map +} + +export namespace RPCCallProgress { + export interface RPCCallProgress$dataEntry { + key: string + value: string + } + + export namespace RPCCallProgress$dataEntry { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key ?? '') + } + + if (opts.writeDefaults === true || (obj.value != null && obj.value !== '')) { + w.uint32(18) + w.string(obj.value ?? '') + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '', + value: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + case 2: + obj.value = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, RPCCallProgress$dataEntry.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): RPCCallProgress$dataEntry => { + return decodeMessage(buf, RPCCallProgress$dataEntry.codec()) + } + } + + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || (obj.event != null && obj.event !== '')) { + w.uint32(10) + w.string(obj.event ?? '') + } + + if (obj.data != null && obj.data.size !== 0) { + for (const [key, value] of obj.data.entries()) { + w.uint32(34) + RPCCallProgress.RPCCallProgress$dataEntry.codec().encode({ key, value }, w, { + writeDefaults: true + }) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + event: '', + data: new Map() + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.event = reader.string() + break + case 4: { + const entry = RPCCallProgress.RPCCallProgress$dataEntry.codec().decode(reader, reader.uint32()) + obj.data.set(entry.key, entry.value) + break + } + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, RPCCallProgress.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): RPCCallProgress => { + return decodeMessage(buf, RPCCallProgress.codec()) + } +} diff --git a/packages/rpc-protocol/tsconfig.json b/packages/rpc-protocol/tsconfig.json new file mode 100644 index 0000000..f67b4ce --- /dev/null +++ b/packages/rpc-protocol/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist", + "noUnusedLocals": false + }, + "include": [ + "src", + "test" + ] +} diff --git a/packages/rpc-server/.aegir.js b/packages/rpc-server/.aegir.js new file mode 100644 index 0000000..e9c18f3 --- /dev/null +++ b/packages/rpc-server/.aegir.js @@ -0,0 +1,6 @@ + +export default { + build: { + bundle: false + } +} diff --git a/packages/rpc-server/LICENSE b/packages/rpc-server/LICENSE new file mode 100644 index 0000000..20ce483 --- /dev/null +++ b/packages/rpc-server/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/rpc-server/LICENSE-APACHE b/packages/rpc-server/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/packages/rpc-server/LICENSE-APACHE @@ -0,0 +1,5 @@ +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. diff --git a/packages/rpc-server/LICENSE-MIT b/packages/rpc-server/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/packages/rpc-server/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/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. diff --git a/packages/rpc-server/README.md b/packages/rpc-server/README.md new file mode 100644 index 0000000..4bd1794 --- /dev/null +++ b/packages/rpc-server/README.md @@ -0,0 +1,53 @@ +# @helia/rpc-server + +[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) +[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech) +[![codecov](https://img.shields.io/codecov/c/github/ipfs/helia-cli.svg?style=flat-square)](https://codecov.io/gh/ipfs/helia-cli) +[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/helia-cli/js-test-and-release.yml?branch=main\&style=flat-square)](https://github.com/ipfs/helia-cli/actions/workflows/js-test-and-release.yml?query=branch%3Amain) + +> An implementation of IPFS in JavaScript + +## Table of contents + +- [Install](#install) + - [Browser ` +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribute + +Contributions welcome! Please check out [the issues](https://github.com/ipfs/helia-cli/issues). + +Also see our [contributing document](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) for more information on how we work, and about contributing in general. + +Please be aware that all interactions related to this repo are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) diff --git a/packages/rpc-server/package.json b/packages/rpc-server/package.json new file mode 100644 index 0000000..d1f1ba8 --- /dev/null +++ b/packages/rpc-server/package.json @@ -0,0 +1,159 @@ +{ + "name": "@helia/rpc-server", + "version": "0.0.0", + "description": "An implementation of IPFS in JavaScript", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/ipfs/helia-cli/tree/master/packages/rpc-server#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/ipfs/helia-cli.git" + }, + "bugs": { + "url": "https://github.com/ipfs/helia-cli/issues" + }, + "keywords": [ + "IPFS" + ], + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "main" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "test": "aegir test", + "test:chrome": "aegir test -t browser --cov", + "test:chrome-webworker": "aegir test -t webworker", + "test:firefox": "aegir test -t browser -- --browser firefox", + "test:firefox-webworker": "aegir test -t webworker -- --browser firefox", + "test:node": "aegir test -t node --cov", + "test:electron-main": "aegir test -t electron-main", + "release": "aegir release" + }, + "dependencies": { + "@helia/interface": "next", + "@helia/rpc-protocol": "~0.0.0", + "@libp2p/interface-keychain": "^2.0.3", + "@libp2p/interface-peer-id": "^2.0.1", + "@libp2p/logger": "^2.0.5", + "@libp2p/peer-id": "^2.0.0", + "@multiformats/multiaddr": "^11.1.5", + "@ucans/ucans": "^0.11.0-alpha", + "it-pb-stream": "^2.0.3", + "multiformats": "^11.0.1", + "uint8arrays": "^4.0.3" + }, + "devDependencies": { + "aegir": "^38.1.0" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/rpc-server/src/handlers/authorization/get.ts b/packages/rpc-server/src/handlers/authorization/get.ts new file mode 100644 index 0000000..ffad848 --- /dev/null +++ b/packages/rpc-server/src/handlers/authorization/get.ts @@ -0,0 +1,103 @@ +import { GetRequest, GetResponse } from '@helia/rpc-protocol/authorization' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import * as ucans from '@ucans/ucans' +import { base58btc } from 'multiformats/bases/base58' +import { concat as uint8ArrayConcat } from 'uint8arrays/concat' + +export function createAuthorizationGet (config: RPCServerConfig): Service { + if (config.helia.libp2p.peerId.privateKey == null || config.helia.libp2p.peerId.publicKey == null) { + throw new Error('Public/private key missing from peer id') + } + + const issuer = new ucans.EdKeypair( + config.helia.libp2p.peerId.privateKey.subarray(4), + config.helia.libp2p.peerId.publicKey.subarray(4), + false + ) + + return { + insecure: true, + async handle ({ peerId, stream }): Promise { + const request = await stream.readPB(GetRequest) + const user = request.user + + const allowedPeerId = await config.users.exportPeerId(`rpc-user-${user}`) + + if (!allowedPeerId.equals(peerId)) { + throw new Error('PeerIds did not match') + } + + if (peerId.publicKey == null) { + throw new Error('Public key component missing') + } + + // derive the audience from the peer id + const audience = `did:key:${base58btc.encode(uint8ArrayConcat([ + Uint8Array.from([0xed, 0x01]), + peerId.publicKey.subarray(4) + ], peerId.publicKey.length - 2))}` + + // authorize the remote peer for these operations + const ucan = await ucans.build({ + audience, + issuer, + lifetimeInSeconds: config.authorizationValiditySeconds, + capabilities: [ + { + with: { scheme: 'helia-rpc', hierPart: '/blockstore/batch' }, + can: { namespace: 'helia-rpc', segments: ['INVOKE'] } + }, + { + with: { scheme: 'helia-rpc', hierPart: '/blockstore/close' }, + can: { namespace: 'helia-rpc', segments: ['INVOKE'] } + }, + { + with: { scheme: 'helia-rpc', hierPart: '/blockstore/delete-many' }, + can: { namespace: 'helia-rpc', segments: ['INVOKE'] } + }, + { + with: { scheme: 'helia-rpc', hierPart: '/blockstore/delete' }, + can: { namespace: 'helia-rpc', segments: ['INVOKE'] } + }, + { + with: { scheme: 'helia-rpc', hierPart: '/blockstore/get-many' }, + can: { namespace: 'helia-rpc', segments: ['INVOKE'] } + }, + { + with: { scheme: 'helia-rpc', hierPart: '/blockstore/get' }, + can: { namespace: 'helia-rpc', segments: ['INVOKE'] } + }, + { + with: { scheme: 'helia-rpc', hierPart: '/blockstore/has' }, + can: { namespace: 'helia-rpc', segments: ['INVOKE'] } + }, + { + with: { scheme: 'helia-rpc', hierPart: '/blockstore/put-many' }, + can: { namespace: 'helia-rpc', segments: ['INVOKE'] } + }, + { + with: { scheme: 'helia-rpc', hierPart: '/blockstore/put' }, + can: { namespace: 'helia-rpc', segments: ['INVOKE'] } + }, + { + with: { scheme: 'helia-rpc', hierPart: '/blockstore/query-keys' }, + can: { namespace: 'helia-rpc', segments: ['INVOKE'] } + }, + { + with: { scheme: 'helia-rpc', hierPart: '/blockstore/query' }, + can: { namespace: 'helia-rpc', segments: ['INVOKE'] } + } + ] + }) + + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: GetResponse.encode({ + authorization: ucans.encode(ucan) + }) + }, + RPCCallMessage) + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/batch.ts b/packages/rpc-server/src/handlers/blockstore/batch.ts new file mode 100644 index 0000000..f76dd93 --- /dev/null +++ b/packages/rpc-server/src/handlers/blockstore/batch.ts @@ -0,0 +1,40 @@ +import { BatchRequest, BatchRequestDelete, BatchRequestPut, BatchRequestType } from '@helia/rpc-protocol/blockstore' +import type { RPCServerConfig, Service } from '../../index.js' +import { CID } from 'multiformats/cid' + +export function createBlockstoreBatch (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const batch = config.helia.blockstore.batch() + + while (true) { + const request = await stream.readPB(BatchRequest) + + for (let i = 0; i < 10; i++) { + if (i < 5) { + continue + } + } + + let putMessage + let deleteMessage + + switch (request.type) { + case BatchRequestType.BATCH_REQUEST_PUT: + putMessage = BatchRequestPut.decode(request.message) + batch.put(CID.decode(putMessage.cid), putMessage.block) + break + case BatchRequestType.BATCH_REQUEST_DELETE: + deleteMessage = BatchRequestDelete.decode(request.message) + batch.delete(CID.decode(deleteMessage.cid)) + break + case BatchRequestType.BATCH_REQUEST_COMMIT: + await batch.commit() + return + default: + throw new Error('Unkown batch message type') + } + } + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/close.ts b/packages/rpc-server/src/handlers/blockstore/close.ts new file mode 100644 index 0000000..b5e2956 --- /dev/null +++ b/packages/rpc-server/src/handlers/blockstore/close.ts @@ -0,0 +1,9 @@ +import type { RPCServerConfig, Service } from '../../index.js' + +export function createBlockstoreClose (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + await config.helia.blockstore.close() + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/delete-many.ts b/packages/rpc-server/src/handlers/blockstore/delete-many.ts new file mode 100644 index 0000000..09b5e9e --- /dev/null +++ b/packages/rpc-server/src/handlers/blockstore/delete-many.ts @@ -0,0 +1,32 @@ +import { DeleteManyOptions, DeleteManyRequest, DeleteManyResponse } from '@helia/rpc-protocol/blockstore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { CID } from 'multiformats/cid' + +export function createBlockstoreDeleteMany (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = DeleteManyOptions.decode(options) + + for await (const cid of config.helia.blockstore.deleteMany( + (async function * () { + while (true) { + const request = await stream.readPB(DeleteManyRequest) + + yield CID.decode(request.cid) + } + })(), { + signal, + ...opts + })) { + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: DeleteManyResponse.encode({ + cid: cid.bytes + }) + }, + RPCCallMessage) + } + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/delete.ts b/packages/rpc-server/src/handlers/blockstore/delete.ts new file mode 100644 index 0000000..4895eb2 --- /dev/null +++ b/packages/rpc-server/src/handlers/blockstore/delete.ts @@ -0,0 +1,26 @@ +import { DeleteOptions, DeleteRequest, DeleteResponse } from '@helia/rpc-protocol/blockstore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { CID } from 'multiformats/cid' + +export function createBlockstoreDelete (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = DeleteOptions.decode(options) + const request = await stream.readPB(DeleteRequest) + const cid = CID.decode(request.cid) + + await config.helia.blockstore.delete(cid, { + signal, + ...opts + }) + + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: DeleteResponse.encode({ + }) + }, + RPCCallMessage) + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/get-many.ts b/packages/rpc-server/src/handlers/blockstore/get-many.ts new file mode 100644 index 0000000..0188afa --- /dev/null +++ b/packages/rpc-server/src/handlers/blockstore/get-many.ts @@ -0,0 +1,32 @@ +import { DeleteManyOptions, DeleteManyRequest, DeleteManyResponse } from '@helia/rpc-protocol/blockstore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { CID } from 'multiformats/cid' + +export function createBlockstoreGetMany (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = DeleteManyOptions.decode(options) + + for await (const cid of config.helia.blockstore.deleteMany( + (async function * () { + while (true) { + const request = await stream.readPB(DeleteManyRequest) + + yield CID.decode(request.cid) + } + })(), { + signal, + ...opts + })) { + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: DeleteManyResponse.encode({ + cid: cid.bytes + }) + }, + RPCCallMessage) + } + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/get.ts b/packages/rpc-server/src/handlers/blockstore/get.ts new file mode 100644 index 0000000..41f31e7 --- /dev/null +++ b/packages/rpc-server/src/handlers/blockstore/get.ts @@ -0,0 +1,27 @@ +import { GetOptions, GetRequest, GetResponse } from '@helia/rpc-protocol/blockstore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { CID } from 'multiformats/cid' + +export function createBlockstoreGet (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = GetOptions.decode(options) + const request = await stream.readPB(GetRequest) + const cid = CID.decode(request.cid) + + const block = await config.helia.blockstore.get(cid, { + signal, + ...opts + }) + + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: GetResponse.encode({ + block + }) + }, + RPCCallMessage) + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/has.ts b/packages/rpc-server/src/handlers/blockstore/has.ts new file mode 100644 index 0000000..17cb0c0 --- /dev/null +++ b/packages/rpc-server/src/handlers/blockstore/has.ts @@ -0,0 +1,27 @@ +import { HasOptions, HasRequest, HasResponse } from '@helia/rpc-protocol/blockstore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { CID } from 'multiformats/cid' + +export function createBlockstoreHas (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = HasOptions.decode(options) + const request = await stream.readPB(HasRequest) + const cid = CID.decode(request.cid) + + const has = await config.helia.blockstore.has(cid, { + signal, + ...opts + }) + + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: HasResponse.encode({ + has + }) + }, + RPCCallMessage) + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/open.ts b/packages/rpc-server/src/handlers/blockstore/open.ts new file mode 100644 index 0000000..606b6b9 --- /dev/null +++ b/packages/rpc-server/src/handlers/blockstore/open.ts @@ -0,0 +1,9 @@ +import type { RPCServerConfig, Service } from '../../index.js' + +export function createBlockstoreOpen (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + await config.helia.blockstore.open() + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/put-many.ts b/packages/rpc-server/src/handlers/blockstore/put-many.ts new file mode 100644 index 0000000..09cf253 --- /dev/null +++ b/packages/rpc-server/src/handlers/blockstore/put-many.ts @@ -0,0 +1,32 @@ +import { DeleteManyOptions, DeleteManyRequest, DeleteManyResponse } from '@helia/rpc-protocol/blockstore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { CID } from 'multiformats/cid' + +export function createBlockstorePutMany (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = DeleteManyOptions.decode(options) + + for await (const cid of config.helia.blockstore.deleteMany( + (async function * () { + while (true) { + const request = await stream.readPB(DeleteManyRequest) + + yield CID.decode(request.cid) + } + })(), { + signal, + ...opts + })) { + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: DeleteManyResponse.encode({ + cid: cid.bytes + }) + }, + RPCCallMessage) + } + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/put.ts b/packages/rpc-server/src/handlers/blockstore/put.ts new file mode 100644 index 0000000..cb967c2 --- /dev/null +++ b/packages/rpc-server/src/handlers/blockstore/put.ts @@ -0,0 +1,26 @@ +import { PutOptions, PutRequest, PutResponse } from '@helia/rpc-protocol/blockstore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { CID } from 'multiformats/cid' + +export function createBlockstorePut (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = PutOptions.decode(options) + const request = await stream.readPB(PutRequest) + const cid = CID.decode(request.cid) + + await config.helia.blockstore.put(cid, request.block, { + signal, + ...opts + }) + + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: PutResponse.encode({ + }) + }, + RPCCallMessage) + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/query-keys.ts b/packages/rpc-server/src/handlers/blockstore/query-keys.ts new file mode 100644 index 0000000..0afb079 --- /dev/null +++ b/packages/rpc-server/src/handlers/blockstore/query-keys.ts @@ -0,0 +1,25 @@ +import { QueryKeysOptions, QueryKeysResponse } from '@helia/rpc-protocol/blockstore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' + +export function createBlockstoreQueryKeys (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = QueryKeysOptions.decode(options) + + for await (const cid of config.helia.blockstore.queryKeys({ + ...opts + }, { + signal + })) { + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: QueryKeysResponse.encode({ + key: cid.bytes + }) + }, + RPCCallMessage) + } + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/query.ts b/packages/rpc-server/src/handlers/blockstore/query.ts new file mode 100644 index 0000000..3c41c85 --- /dev/null +++ b/packages/rpc-server/src/handlers/blockstore/query.ts @@ -0,0 +1,26 @@ +import { QueryOptions, QueryResponse } from '@helia/rpc-protocol/blockstore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' + +export function createBlockstoreQuery (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = QueryOptions.decode(options) + + for await (const { key, value } of config.helia.blockstore.query({ + ...opts + }, { + signal + })) { + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: QueryResponse.encode({ + key: key.bytes, + value + }) + }, + RPCCallMessage) + } + } + } +} diff --git a/packages/rpc-server/src/handlers/index.ts b/packages/rpc-server/src/handlers/index.ts new file mode 100644 index 0000000..1856e16 --- /dev/null +++ b/packages/rpc-server/src/handlers/index.ts @@ -0,0 +1,36 @@ +import type { RPCServerConfig, Service } from '../index.js' +import { createInfo } from './info.js' +import { createAuthorizationGet } from './authorization/get.js' +import { createBlockstoreDelete } from './blockstore/delete.js' +import { createBlockstoreGet } from './blockstore/get.js' +import { createBlockstoreHas } from './blockstore/has.js' +import { createBlockstorePut } from './blockstore/put.js' +import { createBlockstoreDeleteMany } from './blockstore/delete-many.js' +import { createBlockstoreGetMany } from './blockstore/get-many.js' +import { createBlockstoreBatch } from './blockstore/batch.js' +import { createBlockstoreClose } from './blockstore/close.js' +import { createBlockstoreOpen } from './blockstore/open.js' +import { createBlockstorePutMany } from './blockstore/put-many.js' +import { createBlockstoreQueryKeys } from './blockstore/query-keys.js' +import { createBlockstoreQuery } from './blockstore/query.js' + +export function createServices (config: RPCServerConfig): Record { + const services: Record = { + '/authorization/get': createAuthorizationGet(config), + '/blockstore/batch': createBlockstoreBatch(config), + '/blockstore/close': createBlockstoreClose(config), + '/blockstore/delete-many': createBlockstoreDeleteMany(config), + '/blockstore/delete': createBlockstoreDelete(config), + '/blockstore/get-many': createBlockstoreGetMany(config), + '/blockstore/get': createBlockstoreGet(config), + '/blockstore/has': createBlockstoreHas(config), + '/blockstore/open': createBlockstoreOpen(config), + '/blockstore/put-many': createBlockstorePutMany(config), + '/blockstore/put': createBlockstorePut(config), + '/blockstore/query-keys': createBlockstoreQueryKeys(config), + '/blockstore/query': createBlockstoreQuery(config), + '/info': createInfo(config) + } + + return services +} diff --git a/packages/rpc-server/src/handlers/info.ts b/packages/rpc-server/src/handlers/info.ts new file mode 100644 index 0000000..0a496f7 --- /dev/null +++ b/packages/rpc-server/src/handlers/info.ts @@ -0,0 +1,27 @@ +import { InfoOptions, InfoResponse } from '@helia/rpc-protocol/root' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import { peerIdFromString } from '@libp2p/peer-id' +import type { RPCServerConfig, Service } from '../index.js' + +export function createInfo (config: RPCServerConfig): Service { + return { + insecure: true, + async handle ({ options, stream, signal }): Promise { + const opts = InfoOptions.decode(options) + + const result = await config.helia.info({ + peerId: opts.peerId != null ? peerIdFromString(opts.peerId) : undefined, + signal + }) + + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: InfoResponse.encode({ + ...result, + peerId: result.peerId.toString(), + multiaddrs: result.multiaddrs.map(ma => ma.toString()) + }) + }, RPCCallMessage) + } + } +} diff --git a/packages/rpc-server/src/index.ts b/packages/rpc-server/src/index.ts new file mode 100644 index 0000000..ac66503 --- /dev/null +++ b/packages/rpc-server/src/index.ts @@ -0,0 +1,144 @@ +import type { Helia } from '@helia/interface' +import { HeliaError } from '@helia/interface/errors' +import { logger } from '@libp2p/logger' +import { HELIA_RPC_PROTOCOL } from '@helia/rpc-protocol' +import { RPCCallRequest, RPCCallError, RPCCallMessageType, RPCCallMessage } from '@helia/rpc-protocol/rpc' +import * as ucans from '@ucans/ucans' +import { pbStream, ProtobufStream } from 'it-pb-stream' +import { EdKeypair } from '@ucans/ucans' +import type { KeyChain } from '@libp2p/interface-keychain' +import { base58btc } from 'multiformats/bases/base58' +import { concat as uint8ArrayConcat } from 'uint8arrays/concat' +import type { PeerId } from '@libp2p/interface-peer-id' +import { createServices } from './handlers/index.js' + +const log = logger('helia:rpc-server') + +export interface RPCServerConfig { + helia: Helia + users: KeyChain + authorizationValiditySeconds: number +} + +export interface UnaryResponse { + value: ResponseType + metadata: Record +} + +export interface ServiceArgs { + peerId: PeerId + options: Uint8Array + stream: ProtobufStream + signal: AbortSignal +} + +export interface Service { + insecure?: true + handle: (args: ServiceArgs) => Promise +} + +class RPCError extends HeliaError { + constructor (message: string, code: string) { + super(message, 'RPCError', code) + } +} + +export async function createHeliaRpcServer (config: RPCServerConfig): Promise { + const { helia } = config + + if (helia.libp2p.peerId.privateKey == null || helia.libp2p.peerId.publicKey == null) { + // should never happen + throw new Error('helia.libp2p.peerId was missing public or private key component') + } + + const serverKey = new EdKeypair( + helia.libp2p.peerId.privateKey.subarray(4), + helia.libp2p.peerId.publicKey.subarray(4), + false + ) + + const services = createServices(config) + + await helia.libp2p.handle(HELIA_RPC_PROTOCOL, ({ stream, connection }) => { + const controller = new AbortController() + + void Promise.resolve().then(async () => { + const pb = pbStream(stream) + + try { + const request = await pb.readPB(RPCCallRequest) + const service = services[request.resource] + + if (service == null) { + log('no handler defined for %s %s', request.method, request.resource) + throw new RPCError(`Request path "${request.resource}" unimplemented`, 'ERR_PATH_UNIMPLEMENTED') + } + + log('incoming RPC request %s %s', request.method, request.resource) + + if (service.insecure == null) { + if (request.authorization == null) { + log('authorization missing for %s %s', request.method, request.resource) + throw new RPCError(`Authorisation failed for ${request.method} ${request.resource}`, 'ERR_AUTHORIZATION_FAILED') + } + + log('authorizing request %s %s', request.method, request.resource) + + const peerId = connection.remotePeer + + if (peerId.publicKey == null) { + log('public key missing for %s %s', request.method, request.resource) + throw new RPCError(`Authorisation failed for ${request.method} ${request.resource}`, 'ERR_AUTHORIZATION_FAILED') + } + + const audience = `did:key:${base58btc.encode(uint8ArrayConcat([ + Uint8Array.from([0xed, 0x01]), + peerId.publicKey.subarray(4) + ], peerId.publicKey.length - 2))}` + + // authorize request + const result = await ucans.verify(request.authorization, { + audience, + requiredCapabilities: [{ + capability: { + with: { scheme: 'helia-rpc', hierPart: request.resource }, + can: { namespace: 'helia-rpc', segments: [request.method] } + }, + rootIssuer: serverKey.did() + }] + }) + + if (!result.ok) { + log('authorization failed for %s %s', request.method, request.resource) + throw new RPCError(`Authorisation failed for ${request.method} ${request.resource}`, 'ERR_AUTHORIZATION_FAILED') + } + } + + await service.handle({ + peerId: connection.remotePeer, + options: request.options ?? new Uint8Array(), + signal: controller.signal, + stream: pb + }) + log('handler succeeded for %s %s', request.method, request.resource) + + pb.writePB({ + type: RPCCallMessageType.RPC_CALL_DONE + }, RPCCallMessage) + } catch (err: any) { + log.error('handler failed', err) + pb.writePB({ + type: RPCCallMessageType.RPC_CALL_ERROR, + message: RPCCallError.encode({ + name: err.name, + message: err.message, + stack: err.stack, + code: err.code + }) + }, RPCCallMessage) + } finally { + stream.closeWrite() + } + }) + }) +} diff --git a/packages/rpc-server/src/utils/multiaddr-to-url.ts b/packages/rpc-server/src/utils/multiaddr-to-url.ts new file mode 100644 index 0000000..a967130 --- /dev/null +++ b/packages/rpc-server/src/utils/multiaddr-to-url.ts @@ -0,0 +1,22 @@ +import type { Multiaddr } from '@multiformats/multiaddr' +import { InvalidParametersError } from '@helia/interface/errors' + +export function multiaddrToUrl (addr: Multiaddr): URL { + const protoNames = addr.protoNames() + + if (protoNames.length !== 3) { + throw new InvalidParametersError('Helia RPC address format incorrect') + } + + if (protoNames[0] !== 'ip4' && protoNames[0] !== 'ip6') { + throw new InvalidParametersError('Helia RPC address format incorrect') + } + + if (protoNames[1] !== 'tcp' && protoNames[2] !== 'ws') { + throw new InvalidParametersError('Helia RPC address format incorrect') + } + + const { host, port } = addr.toOptions() + + return new URL(`ws://${host}:${port}`) +} diff --git a/packages/rpc-server/test/index.spec.ts b/packages/rpc-server/test/index.spec.ts new file mode 100644 index 0000000..97bedd1 --- /dev/null +++ b/packages/rpc-server/test/index.spec.ts @@ -0,0 +1,7 @@ +import '../src/index.js' + +describe('rpc-server', () => { + it('should work', async () => { + + }) +}) diff --git a/packages/rpc-server/tsconfig.json b/packages/rpc-server/tsconfig.json new file mode 100644 index 0000000..447a80d --- /dev/null +++ b/packages/rpc-server/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../rpc-protocol" + } + ] +} diff --git a/packages/unixfs-cli/.aegir.js b/packages/unixfs-cli/.aegir.js new file mode 100644 index 0000000..e9c18f3 --- /dev/null +++ b/packages/unixfs-cli/.aegir.js @@ -0,0 +1,6 @@ + +export default { + build: { + bundle: false + } +} diff --git a/packages/unixfs-cli/LICENSE b/packages/unixfs-cli/LICENSE new file mode 100644 index 0000000..20ce483 --- /dev/null +++ b/packages/unixfs-cli/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/unixfs-cli/LICENSE-APACHE b/packages/unixfs-cli/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/packages/unixfs-cli/LICENSE-APACHE @@ -0,0 +1,5 @@ +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. diff --git a/packages/unixfs-cli/LICENSE-MIT b/packages/unixfs-cli/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/packages/unixfs-cli/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/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. diff --git a/packages/unixfs-cli/README.md b/packages/unixfs-cli/README.md new file mode 100644 index 0000000..bc03e0a --- /dev/null +++ b/packages/unixfs-cli/README.md @@ -0,0 +1,44 @@ +# @helia/unixfs-cli + +[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) +[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech) +[![codecov](https://img.shields.io/codecov/c/github/ipfs/helia-cli.svg?style=flat-square)](https://codecov.io/gh/ipfs/helia-cli) +[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/helia-cli/js-test-and-release.yml?branch=main\&style=flat-square)](https://github.com/ipfs/helia-cli/actions/workflows/js-test-and-release.yml?query=branch%3Amain) + +> Run unixfs commands against a Helia node on the CLI + +## Table of contents + +- [Install](#install) +- [API Docs](#api-docs) +- [License](#license) +- [Contribute](#contribute) + +## Install + +```console +$ npm i @helia/unixfs-cli +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribute + +Contributions welcome! Please check out [the issues](https://github.com/ipfs/helia-cli/issues). + +Also see our [contributing document](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) for more information on how we work, and about contributing in general. + +Please be aware that all interactions related to this repo are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) diff --git a/packages/unixfs-cli/package.json b/packages/unixfs-cli/package.json new file mode 100644 index 0000000..631e1ee --- /dev/null +++ b/packages/unixfs-cli/package.json @@ -0,0 +1,156 @@ +{ + "name": "@helia/unixfs-cli", + "version": "0.0.0", + "description": "Run unixfs commands against a Helia node on the CLI", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/ipfs/helia-cli/tree/master/packages/unixfs-cli#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/ipfs/helia-cli.git" + }, + "bugs": { + "url": "https://github.com/ipfs/helia-cli/issues" + }, + "keywords": [ + "IPFS" + ], + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "bin": { + "unixfs": "./dist/src/index.js" + }, + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "main" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "test": "aegir test", + "test:node": "aegir test -t node --cov", + "release": "aegir release" + }, + "dependencies": { + "@helia/cli-utils": "~0.0.0", + "@helia/unixfs": "next", + "@libp2p/interfaces": "^3.3.1", + "ipfs-unixfs": "^9.0.0", + "ipfs-unixfs-exporter": "^10.0.0", + "ipfs-unixfs-importer": "^12.0.0", + "it-glob": "^2.0.0", + "it-merge": "^2.0.0", + "kleur": "^4.1.5", + "multiformats": "^11.0.1" + }, + "devDependencies": { + "aegir": "^38.1.0" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/unixfs-cli/src/commands/add.ts b/packages/unixfs-cli/src/commands/add.ts new file mode 100644 index 0000000..b03ae38 --- /dev/null +++ b/packages/unixfs-cli/src/commands/add.ts @@ -0,0 +1,108 @@ +import { unixfs } from '@helia/unixfs' +import merge from 'it-merge' +import path from 'node:path' +import { globSource } from '../utils/glob-source.js' +import fs from 'node:fs' +import { dateToMtime } from '../utils/date-to-mtime.js' +import type { Mtime } from 'ipfs-unixfs' +import type { ImportCandidate, UserImporterOptions } from 'ipfs-unixfs-importer' +import type { Command } from '@helia/cli-utils' + +interface AddArgs { + positionals: string[] + fs: string +} + +export const add: Command = { + command: 'add', + description: 'Add a file or directory to your helia node', + example: '$ unixfs add path/to/file.txt', + async execute ({ positionals, helia, stdout }) { + const options: UserImporterOptions = { + cidVersion: 1, + rawLeaves: true + } + const fs = unixfs(helia) + + for await (const result of fs.addStream(parsePositionals(positionals), options)) { + stdout.write(`${result.cid}\n`) + } + } +} + +async function * parsePositionals (positionals: string[], mode?: number, mtime?: Mtime, hidden?: boolean, recursive?: boolean, preserveMode?: boolean, preserveMtime?: boolean): AsyncGenerator { + if (positionals.length === 0) { + yield { + content: process.stdin, + mode, + mtime + } + return + } + + yield * merge(...positionals.map(file => getSource(file, { + hidden, + recursive, + preserveMode, + preserveMtime, + mode, + mtime + }))) +} + +interface SourceOptions { + hidden?: boolean + recursive?: boolean + preserveMode?: boolean + preserveMtime?: boolean + mode?: number + mtime?: Mtime +} + +async function * getSource (target: string, options: SourceOptions = {}): AsyncGenerator { + const absolutePath = path.resolve(target) + const stats = await fs.promises.stat(absolutePath) + + if (stats.isFile()) { + let mtime = options.mtime + let mode = options.mode + + if (options.preserveMtime === true) { + mtime = dateToMtime(stats.mtime) + } + + if (options.preserveMode === true) { + mode = stats.mode + } + + yield { + path: path.basename(target), + content: fs.createReadStream(absolutePath), + mtime, + mode + } + + return + } + + const dirName = path.basename(absolutePath) + + let pattern = '*' + + if (options.recursive === true) { + pattern = '**/*' + } + + for await (const content of globSource(target, pattern, { + hidden: options.hidden, + preserveMode: options.preserveMode, + preserveMtime: options.preserveMtime, + mode: options.mode, + mtime: options.mtime + })) { + yield { + ...content, + path: `${dirName}${content.path}` + } + } +} diff --git a/packages/unixfs-cli/src/commands/cat.ts b/packages/unixfs-cli/src/commands/cat.ts new file mode 100644 index 0000000..eb34eaa --- /dev/null +++ b/packages/unixfs-cli/src/commands/cat.ts @@ -0,0 +1,51 @@ +import type { Command } from '@helia/cli-utils' +import { exporter } from 'ipfs-unixfs-exporter' +import { CID } from 'multiformats/cid' + +interface CatArgs { + positionals?: string[] + offset?: string + length?: string +} + +export const cat: Command = { + command: 'cat', + description: 'Fetch and cat an IPFS path referencing a file', + example: '$ unixfs cat ', + options: { + offset: { + description: 'Where to start reading the file from', + type: 'string', + short: 'o' + }, + length: { + description: 'How many bytes to read from the file', + type: 'string', + short: 'l' + }, + progress: { + description: 'Display information about how the CID is being resolved', + type: 'boolean', + short: 'p' + } + }, + async execute ({ positionals, offset, length, helia, stdout }) { + if (positionals == null || positionals.length === 0) { + throw new TypeError('Missing positionals') + } + + const cid = CID.parse(positionals[0]) + const entry = await exporter(cid, helia.blockstore, { + offset: offset != null ? Number(offset) : undefined, + length: length != null ? Number(length) : undefined + }) + + if (entry.type !== 'file' && entry.type !== 'raw') { + throw new Error('UnixFS path was not a file') + } + + for await (const buf of entry.content()) { + stdout.write(buf) + } + } +} diff --git a/packages/unixfs-cli/src/commands/index.ts b/packages/unixfs-cli/src/commands/index.ts new file mode 100644 index 0000000..d20b350 --- /dev/null +++ b/packages/unixfs-cli/src/commands/index.ts @@ -0,0 +1,10 @@ +import type { Command } from '@helia/cli-utils' +import { add } from './add.js' +import { cat } from './cat.js' +import { stat } from './stat.js' + +export const commands: Array> = [ + add, + cat, + stat +] diff --git a/packages/unixfs-cli/src/commands/stat.ts b/packages/unixfs-cli/src/commands/stat.ts new file mode 100644 index 0000000..38f21ca --- /dev/null +++ b/packages/unixfs-cli/src/commands/stat.ts @@ -0,0 +1,55 @@ +import type { Command } from '@helia/cli-utils' +import { exporter } from 'ipfs-unixfs-exporter' +import { CID } from 'multiformats/cid' +import * as format from '@helia/cli-utils/format' +import type { Formatable } from '@helia/cli-utils/format' + +interface StatArgs { + positionals?: string[] + explain?: boolean +} + +export const stat: Command = { + command: 'stat', + description: 'Display statistics about a dag', + example: '$ unixfs stat ', + options: { + explain: { + description: 'Print diagnostic information while trying to resolve the block', + type: 'boolean', + default: false + } + }, + async execute ({ positionals, helia, stdout, explain }) { + if (positionals == null || positionals.length === 0) { + throw new TypeError('Missing positionals') + } + + let progress: undefined | ((evt: Event) => void) + + if (explain === true) { + progress = (evt: Event) => { + stdout.write(`${evt.type}\n`) + } + } + + const cid = CID.parse(positionals[0]) + const entry = await exporter(cid, helia.blockstore, { + // @ts-expect-error + progress + }) + + const items: Formatable[] = [ + format.table([ + format.row('CID', entry.cid.toString()), + format.row('Type', entry.type), + format.row('Size', `${entry.size}`) + ]) + ] + + format.formatter( + stdout, + items + ) + } +} diff --git a/packages/unixfs-cli/src/index.ts b/packages/unixfs-cli/src/index.ts new file mode 100644 index 0000000..2ce98cd --- /dev/null +++ b/packages/unixfs-cli/src/index.ts @@ -0,0 +1,18 @@ +#! /usr/bin/env node --trace-warnings +/* eslint-disable no-console */ + +import { cli } from '@helia/cli-utils' +import kleur from 'kleur' +import { commands } from './commands/index.js' + +async function main (): Promise { + const command = 'unixfs' + const description = `Run unixfs commands against a ${kleur.bold('Helia')} node` + + await cli(command, description, commands) +} + +main().catch(err => { + console.error(err) // eslint-disable-line no-console + process.exit(1) +}) diff --git a/packages/unixfs-cli/src/utils/date-to-mtime.ts b/packages/unixfs-cli/src/utils/date-to-mtime.ts new file mode 100644 index 0000000..642c34b --- /dev/null +++ b/packages/unixfs-cli/src/utils/date-to-mtime.ts @@ -0,0 +1,11 @@ +import type { Mtime } from 'ipfs-unixfs' + +export function dateToMtime (date: Date): Mtime { + const ms = date.getTime() + const secs = Math.floor(ms / 1000) + + return { + secs, + nsecs: (ms - (secs * 1000)) * 1000 + } +} diff --git a/packages/unixfs-cli/src/utils/glob-source.ts b/packages/unixfs-cli/src/utils/glob-source.ts new file mode 100644 index 0000000..17de99a --- /dev/null +++ b/packages/unixfs-cli/src/utils/glob-source.ts @@ -0,0 +1,95 @@ +import fsp from 'node:fs/promises' +import fs from 'node:fs' +import glob from 'it-glob' +import path from 'path' +import { CodeError } from '@libp2p/interfaces/errors' +import type { Mtime } from 'ipfs-unixfs' +import type { ImportCandidate } from 'ipfs-unixfs-importer' + +export interface GlobSourceOptions { + /** + * Include .dot files in matched paths + */ + hidden?: boolean + + /** + * follow symlinks + */ + followSymlinks?: boolean + + /** + * Preserve mode + */ + preserveMode?: boolean + + /** + * Preserve mtime + */ + preserveMtime?: boolean + + /** + * mode to use - if preserveMode is true this will be ignored + */ + mode?: number + + /** + * mtime to use - if preserveMtime is true this will be ignored + */ + mtime?: Mtime +} + +/** + * Create an async iterator that yields paths that match requested glob pattern + */ +export async function * globSource (cwd: string, pattern: string, options: GlobSourceOptions): AsyncGenerator { + options = options ?? {} + + if (typeof pattern !== 'string') { + throw new CodeError('Pattern must be a string', 'ERR_INVALID_PATH', { pattern }) + } + + if (!path.isAbsolute(cwd)) { + cwd = path.resolve(process.cwd(), cwd) + } + + const globOptions = Object.assign({}, { + nodir: false, + realpath: false, + absolute: true, + dot: Boolean(options.hidden), + follow: options.followSymlinks != null ? options.followSymlinks : true + }) + + for await (const p of glob(cwd, pattern, globOptions)) { + const stat = await fsp.stat(p) + + let mode = options.mode + + if (options.preserveMode === true) { + mode = stat.mode + } + + let mtime = options.mtime + + if (options.preserveMtime === true) { + const ms = stat.mtime.getTime() + const secs = Math.floor(ms / 1000) + + mtime = { + secs, + nsecs: (ms - (secs * 1000)) * 1000 + } + } + + yield { + path: toPosix(p.replace(cwd, '')), + content: stat.isFile() ? fs.createReadStream(p) : undefined, + mode, + mtime + } + } +} + +function toPosix (path: string): string { + return path.replace(/\\/g, '/') +} diff --git a/packages/unixfs-cli/test/index.spec.ts b/packages/unixfs-cli/test/index.spec.ts new file mode 100644 index 0000000..e67dc48 --- /dev/null +++ b/packages/unixfs-cli/test/index.spec.ts @@ -0,0 +1,7 @@ +import { expect } from 'aegir/chai' + +describe('cli', () => { + it('should start a node', () => { + expect(true).to.be.ok() + }) +}) diff --git a/packages/unixfs-cli/tsconfig.json b/packages/unixfs-cli/tsconfig.json new file mode 100644 index 0000000..b2ff54e --- /dev/null +++ b/packages/unixfs-cli/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../cli-utils" + } + ] +} From 36861a23ba18a8cba913ae9565322b428d37232c Mon Sep 17 00:00:00 2001 From: achingbrain Date: Fri, 3 Feb 2023 00:47:00 +0100 Subject: [PATCH 2/3] chore: rename dir --- packages/{helia-cli => cli}/.aegir.js | 0 packages/{helia-cli => cli}/LICENSE | 0 packages/{helia-cli => cli}/LICENSE-APACHE | 0 packages/{helia-cli => cli}/LICENSE-MIT | 0 packages/{helia-cli => cli}/README.md | 0 packages/{helia-cli => cli}/package.json | 0 packages/{helia-cli => cli}/src/commands/daemon.ts | 0 packages/{helia-cli => cli}/src/commands/id.ts | 0 packages/{helia-cli => cli}/src/commands/index.ts | 0 packages/{helia-cli => cli}/src/commands/init.ts | 0 packages/{helia-cli => cli}/src/commands/rpc/index.ts | 0 packages/{helia-cli => cli}/src/commands/rpc/rmuser.ts | 0 packages/{helia-cli => cli}/src/commands/rpc/useradd.ts | 0 packages/{helia-cli => cli}/src/commands/rpc/users.ts | 0 packages/{helia-cli => cli}/src/commands/status.ts | 0 packages/{helia-cli => cli}/src/index.ts | 0 packages/{helia-cli => cli}/test/index.spec.ts | 0 packages/{helia-cli => cli}/tsconfig.json | 0 18 files changed, 0 insertions(+), 0 deletions(-) rename packages/{helia-cli => cli}/.aegir.js (100%) rename packages/{helia-cli => cli}/LICENSE (100%) rename packages/{helia-cli => cli}/LICENSE-APACHE (100%) rename packages/{helia-cli => cli}/LICENSE-MIT (100%) rename packages/{helia-cli => cli}/README.md (100%) rename packages/{helia-cli => cli}/package.json (100%) rename packages/{helia-cli => cli}/src/commands/daemon.ts (100%) rename packages/{helia-cli => cli}/src/commands/id.ts (100%) rename packages/{helia-cli => cli}/src/commands/index.ts (100%) rename packages/{helia-cli => cli}/src/commands/init.ts (100%) rename packages/{helia-cli => cli}/src/commands/rpc/index.ts (100%) rename packages/{helia-cli => cli}/src/commands/rpc/rmuser.ts (100%) rename packages/{helia-cli => cli}/src/commands/rpc/useradd.ts (100%) rename packages/{helia-cli => cli}/src/commands/rpc/users.ts (100%) rename packages/{helia-cli => cli}/src/commands/status.ts (100%) rename packages/{helia-cli => cli}/src/index.ts (100%) rename packages/{helia-cli => cli}/test/index.spec.ts (100%) rename packages/{helia-cli => cli}/tsconfig.json (100%) diff --git a/packages/helia-cli/.aegir.js b/packages/cli/.aegir.js similarity index 100% rename from packages/helia-cli/.aegir.js rename to packages/cli/.aegir.js diff --git a/packages/helia-cli/LICENSE b/packages/cli/LICENSE similarity index 100% rename from packages/helia-cli/LICENSE rename to packages/cli/LICENSE diff --git a/packages/helia-cli/LICENSE-APACHE b/packages/cli/LICENSE-APACHE similarity index 100% rename from packages/helia-cli/LICENSE-APACHE rename to packages/cli/LICENSE-APACHE diff --git a/packages/helia-cli/LICENSE-MIT b/packages/cli/LICENSE-MIT similarity index 100% rename from packages/helia-cli/LICENSE-MIT rename to packages/cli/LICENSE-MIT diff --git a/packages/helia-cli/README.md b/packages/cli/README.md similarity index 100% rename from packages/helia-cli/README.md rename to packages/cli/README.md diff --git a/packages/helia-cli/package.json b/packages/cli/package.json similarity index 100% rename from packages/helia-cli/package.json rename to packages/cli/package.json diff --git a/packages/helia-cli/src/commands/daemon.ts b/packages/cli/src/commands/daemon.ts similarity index 100% rename from packages/helia-cli/src/commands/daemon.ts rename to packages/cli/src/commands/daemon.ts diff --git a/packages/helia-cli/src/commands/id.ts b/packages/cli/src/commands/id.ts similarity index 100% rename from packages/helia-cli/src/commands/id.ts rename to packages/cli/src/commands/id.ts diff --git a/packages/helia-cli/src/commands/index.ts b/packages/cli/src/commands/index.ts similarity index 100% rename from packages/helia-cli/src/commands/index.ts rename to packages/cli/src/commands/index.ts diff --git a/packages/helia-cli/src/commands/init.ts b/packages/cli/src/commands/init.ts similarity index 100% rename from packages/helia-cli/src/commands/init.ts rename to packages/cli/src/commands/init.ts diff --git a/packages/helia-cli/src/commands/rpc/index.ts b/packages/cli/src/commands/rpc/index.ts similarity index 100% rename from packages/helia-cli/src/commands/rpc/index.ts rename to packages/cli/src/commands/rpc/index.ts diff --git a/packages/helia-cli/src/commands/rpc/rmuser.ts b/packages/cli/src/commands/rpc/rmuser.ts similarity index 100% rename from packages/helia-cli/src/commands/rpc/rmuser.ts rename to packages/cli/src/commands/rpc/rmuser.ts diff --git a/packages/helia-cli/src/commands/rpc/useradd.ts b/packages/cli/src/commands/rpc/useradd.ts similarity index 100% rename from packages/helia-cli/src/commands/rpc/useradd.ts rename to packages/cli/src/commands/rpc/useradd.ts diff --git a/packages/helia-cli/src/commands/rpc/users.ts b/packages/cli/src/commands/rpc/users.ts similarity index 100% rename from packages/helia-cli/src/commands/rpc/users.ts rename to packages/cli/src/commands/rpc/users.ts diff --git a/packages/helia-cli/src/commands/status.ts b/packages/cli/src/commands/status.ts similarity index 100% rename from packages/helia-cli/src/commands/status.ts rename to packages/cli/src/commands/status.ts diff --git a/packages/helia-cli/src/index.ts b/packages/cli/src/index.ts similarity index 100% rename from packages/helia-cli/src/index.ts rename to packages/cli/src/index.ts diff --git a/packages/helia-cli/test/index.spec.ts b/packages/cli/test/index.spec.ts similarity index 100% rename from packages/helia-cli/test/index.spec.ts rename to packages/cli/test/index.spec.ts diff --git a/packages/helia-cli/tsconfig.json b/packages/cli/tsconfig.json similarity index 100% rename from packages/helia-cli/tsconfig.json rename to packages/cli/tsconfig.json From 6f68b59dd69a00a159f12125a63bd4042eb3d8e9 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 22 Mar 2023 19:02:12 +0000 Subject: [PATCH 3/3] chore: add datastore methods --- packages/cli-utils/package.json | 14 +- packages/cli-utils/src/create-helia.ts | 15 +- packages/cli-utils/src/find-helia.ts | 3 - packages/cli-utils/src/index.ts | 7 +- packages/cli/package.json | 4 +- packages/cli/src/commands/daemon.ts | 12 +- packages/cli/src/commands/gc.ts | 15 + packages/cli/src/commands/id.ts | 16 - packages/cli/src/commands/index.ts | 4 +- packages/cli/src/commands/init.ts | 9 +- packages/ipns-cli/.aegir.js | 6 + packages/ipns-cli/LICENSE | 4 + packages/ipns-cli/LICENSE-APACHE | 5 + packages/ipns-cli/LICENSE-MIT | 19 + packages/ipns-cli/README.md | 44 + packages/ipns-cli/package.json | 151 +++ packages/ipns-cli/src/commands/index.ts | 8 + packages/ipns-cli/src/commands/publish.ts | 28 + packages/ipns-cli/src/commands/resolve.ts | 26 + packages/ipns-cli/src/index.ts | 18 + packages/ipns-cli/test/index.spec.ts | 7 + packages/ipns-cli/tsconfig.json | 15 + packages/rpc-client/package.json | 6 +- .../src/commands/blockstore/close.ts | 11 - .../src/commands/blockstore/get-all.ts | 25 + .../src/commands/blockstore/open.ts | 11 - .../src/commands/blockstore/put-many.ts | 4 +- .../rpc-client/src/commands/blockstore/put.ts | 4 +- .../{blockstore => datastore}/batch.ts | 20 +- .../src/commands/datastore/delete-many.ts | 22 + .../src/commands/datastore/delete.ts | 18 + .../src/commands/datastore/get-many.ts | 22 + .../rpc-client/src/commands/datastore/get.ts | 32 + .../rpc-client/src/commands/datastore/has.ts | 22 + .../src/commands/datastore/put-many.ts | 23 + .../rpc-client/src/commands/datastore/put.ts | 19 + .../{blockstore => datastore}/query-keys.ts | 10 +- .../{blockstore => datastore}/query.ts | 10 +- packages/rpc-client/src/commands/info.ts | 27 - .../rpc-client/src/commands/utils/rpc-call.ts | 6 +- packages/rpc-client/src/index.ts | 40 +- packages/rpc-protocol/src/authorization.ts | 8 +- packages/rpc-protocol/src/blockstore.proto | 90 +- packages/rpc-protocol/src/blockstore.ts | 995 +++--------------- packages/rpc-protocol/src/datastore.proto | 49 +- packages/rpc-protocol/src/datastore.ts | 656 +++++------- packages/rpc-protocol/src/pins.proto | 54 + packages/rpc-protocol/src/pins.ts | 896 ++++++++++++++++ packages/rpc-protocol/src/root.proto | 16 +- packages/rpc-protocol/src/root.ts | 132 +-- packages/rpc-protocol/src/rpc.ts | 40 +- packages/rpc-server/package.json | 4 +- .../src/handlers/blockstore/close.ts | 9 - .../src/handlers/blockstore/get-all.ts | 25 + .../src/handlers/blockstore/get-many.ts | 12 +- .../src/handlers/blockstore/open.ts | 9 - .../{blockstore => datastore}/batch.ts | 14 +- .../src/handlers/datastore/delete-many.ts | 32 + .../src/handlers/datastore/delete.ts | 26 + .../src/handlers/datastore/get-many.ts | 32 + .../rpc-server/src/handlers/datastore/get.ts | 27 + .../rpc-server/src/handlers/datastore/has.ts | 27 + .../src/handlers/datastore/put-many.ts | 32 + .../rpc-server/src/handlers/datastore/put.ts | 26 + .../{blockstore => datastore}/query-keys.ts | 8 +- .../{blockstore => datastore}/query.ts | 8 +- packages/rpc-server/src/handlers/index.ts | 34 +- packages/rpc-server/src/handlers/info.ts | 27 - packages/rpc-server/src/index.ts | 10 +- .../rpc-server/src/utils/multiaddr-to-url.ts | 7 +- packages/unixfs-cli/package.json | 8 +- packages/unixfs-cli/src/commands/add.ts | 8 +- .../unixfs-cli/src/utils/date-to-mtime.ts | 4 +- packages/unixfs-cli/src/utils/glob-source.ts | 2 +- 74 files changed, 2349 insertions(+), 1740 deletions(-) create mode 100644 packages/cli/src/commands/gc.ts delete mode 100644 packages/cli/src/commands/id.ts create mode 100644 packages/ipns-cli/.aegir.js create mode 100644 packages/ipns-cli/LICENSE create mode 100644 packages/ipns-cli/LICENSE-APACHE create mode 100644 packages/ipns-cli/LICENSE-MIT create mode 100644 packages/ipns-cli/README.md create mode 100644 packages/ipns-cli/package.json create mode 100644 packages/ipns-cli/src/commands/index.ts create mode 100644 packages/ipns-cli/src/commands/publish.ts create mode 100644 packages/ipns-cli/src/commands/resolve.ts create mode 100644 packages/ipns-cli/src/index.ts create mode 100644 packages/ipns-cli/test/index.spec.ts create mode 100644 packages/ipns-cli/tsconfig.json delete mode 100644 packages/rpc-client/src/commands/blockstore/close.ts create mode 100644 packages/rpc-client/src/commands/blockstore/get-all.ts delete mode 100644 packages/rpc-client/src/commands/blockstore/open.ts rename packages/rpc-client/src/commands/{blockstore => datastore}/batch.ts (81%) create mode 100644 packages/rpc-client/src/commands/datastore/delete-many.ts create mode 100644 packages/rpc-client/src/commands/datastore/delete.ts create mode 100644 packages/rpc-client/src/commands/datastore/get-many.ts create mode 100644 packages/rpc-client/src/commands/datastore/get.ts create mode 100644 packages/rpc-client/src/commands/datastore/has.ts create mode 100644 packages/rpc-client/src/commands/datastore/put-many.ts create mode 100644 packages/rpc-client/src/commands/datastore/put.ts rename packages/rpc-client/src/commands/{blockstore => datastore}/query-keys.ts (63%) rename packages/rpc-client/src/commands/{blockstore => datastore}/query.ts (66%) delete mode 100644 packages/rpc-client/src/commands/info.ts create mode 100644 packages/rpc-protocol/src/pins.proto create mode 100644 packages/rpc-protocol/src/pins.ts delete mode 100644 packages/rpc-server/src/handlers/blockstore/close.ts create mode 100644 packages/rpc-server/src/handlers/blockstore/get-all.ts delete mode 100644 packages/rpc-server/src/handlers/blockstore/open.ts rename packages/rpc-server/src/handlers/{blockstore => datastore}/batch.ts (69%) create mode 100644 packages/rpc-server/src/handlers/datastore/delete-many.ts create mode 100644 packages/rpc-server/src/handlers/datastore/delete.ts create mode 100644 packages/rpc-server/src/handlers/datastore/get-many.ts create mode 100644 packages/rpc-server/src/handlers/datastore/get.ts create mode 100644 packages/rpc-server/src/handlers/datastore/has.ts create mode 100644 packages/rpc-server/src/handlers/datastore/put-many.ts create mode 100644 packages/rpc-server/src/handlers/datastore/put.ts rename packages/rpc-server/src/handlers/{blockstore => datastore}/query-keys.ts (74%) rename packages/rpc-server/src/handlers/{blockstore => datastore}/query.ts (75%) delete mode 100644 packages/rpc-server/src/handlers/info.ts diff --git a/packages/cli-utils/package.json b/packages/cli-utils/package.json index a74cf54..ac3c8e9 100644 --- a/packages/cli-utils/package.json +++ b/packages/cli-utils/package.json @@ -173,21 +173,21 @@ "@libp2p/bootstrap": "^6.0.0", "@libp2p/interface-keychain": "^2.0.3", "@libp2p/interface-peer-id": "^2.0.1", - "@libp2p/kad-dht": "^7.0.0", - "@libp2p/keychain": "^1.0.0", + "@libp2p/kad-dht": "^8.0.0", + "@libp2p/keychain": "^2.0.0", "@libp2p/logger": "^2.0.5", "@libp2p/mplex": "^7.1.1", "@libp2p/prometheus-metrics": "1.1.3", "@libp2p/tcp": "^6.0.8", "@libp2p/websockets": "^5.0.2", - "@multiformats/multiaddr": "^11.1.5", + "@multiformats/multiaddr": "^12.1.0", "@ucans/ucans": "^0.11.0-alpha", - "blockstore-datastore-adapter": "^5.0.0", - "datastore-core": "^8.0.4", - "datastore-fs": "^8.0.0", + "blockstore-fs": "^1.0.0", + "datastore-level": "^10.0.1", + "datastore-core": "^9.0.3", "helia": "next", "kleur": "^4.1.5", - "libp2p": "^0.42.2", + "libp2p": "^0.43.2", "strip-json-comments": "^5.0.0" }, "devDependencies": { diff --git a/packages/cli-utils/src/create-helia.ts b/packages/cli-utils/src/create-helia.ts index 7ebe2da..e46afd0 100644 --- a/packages/cli-utils/src/create-helia.ts +++ b/packages/cli-utils/src/create-helia.ts @@ -1,8 +1,8 @@ import type { Helia } from '@helia/interface' import type { HeliaConfig } from './index.js' import { createHelia as createHeliaNode } from 'helia' -import { FsDatastore } from 'datastore-fs' -import { BlockstoreDatastoreAdapter } from 'blockstore-datastore-adapter' +import { LevelDatastore } from 'datastore-level' +import { FsBlockstore } from 'blockstore-fs' import { createLibp2p } from 'libp2p' import { tcp } from '@libp2p/tcp' import { webSockets } from '@libp2p/websockets' @@ -17,8 +17,6 @@ import stripJsonComments from 'strip-json-comments' import fs from 'node:fs' import path from 'node:path' import * as readline from 'node:readline/promises' -import { ShardingDatastore } from 'datastore-core' -import { NextToLast } from 'datastore-core/shard' export async function createHelia (configDir: string, offline: boolean = false): Promise { const config: HeliaConfig = JSON.parse(stripJsonComments(fs.readFileSync(path.join(configDir, 'helia.json'), 'utf-8'))) @@ -32,17 +30,12 @@ export async function createHelia (configDir: string, offline: boolean = false): password = await rl.question('Enter libp2p keychain password: ') } - const datastore = new FsDatastore(config.datastore, { + const datastore = new LevelDatastore(config.datastore, { createIfMissing: true }) await datastore.open() - const blockstore = new BlockstoreDatastoreAdapter( - new ShardingDatastore( - new FsDatastore(config.blockstore), - new NextToLast(2) - ) - ) + const blockstore = new FsBlockstore(config.blockstore) await blockstore.open() const helia = await createHeliaNode({ diff --git a/packages/cli-utils/src/find-helia.ts b/packages/cli-utils/src/find-helia.ts index ea7237a..5018feb 100644 --- a/packages/cli-utils/src/find-helia.ts +++ b/packages/cli-utils/src/find-helia.ts @@ -74,9 +74,6 @@ export async function findOnlineHelia (configDir: string, rpcAddress: string, us yamux(), mplex() ], - relay: { - enabled: false - }, nat: { enabled: false } diff --git a/packages/cli-utils/src/index.ts b/packages/cli-utils/src/index.ts index b46c753..61f9783 100644 --- a/packages/cli-utils/src/index.ts +++ b/packages/cli-utils/src/index.ts @@ -1,6 +1,5 @@ import type { ParseArgsConfig } from 'node:util' import type { Helia } from '@helia/interface' -import { InvalidParametersError } from '@helia/interface/errors' import { parseArgs } from 'node:util' import { findHeliaDir } from './find-helia-dir.js' import path from 'node:path' @@ -196,15 +195,15 @@ export async function cli (command: string, description: string, subcommands: Ar const configDir = rootCommandArgs.values.directory if (configDir == null || typeof configDir !== 'string') { - throw new InvalidParametersError('No config directory specified') + throw new Error('No config directory specified') } if (typeof rootCommandArgs.values.rpcAddress !== 'string') { - throw new InvalidParametersError('No RPC address specified') + throw new Error('No RPC address specified') } if (typeof rootCommandArgs.values.user !== 'string') { - throw new InvalidParametersError('No RPC user specified') + throw new Error('No RPC user specified') } if (rootCommandArgs.values.help === true && rootCommandArgs.positionals.length === 0) { diff --git a/packages/cli/package.json b/packages/cli/package.json index 580bf41..85382cd 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -142,10 +142,10 @@ "@libp2p/crypto": "^1.0.11", "@libp2p/interface-keychain": "^2.0.3", "@libp2p/interface-peer-id": "^2.0.1", - "@libp2p/keychain": "^1.0.0", + "@libp2p/keychain": "^2.0.0", "@libp2p/logger": "^2.0.5", "@libp2p/peer-id-factory": "^2.0.0", - "datastore-fs": "^8.0.0", + "datastore-fs": "^9.0.0", "kleur": "^4.1.5", "uint8arrays": "^4.0.3" }, diff --git a/packages/cli/src/commands/daemon.ts b/packages/cli/src/commands/daemon.ts index 5fad7e0..49d2258 100644 --- a/packages/cli/src/commands/daemon.ts +++ b/packages/cli/src/commands/daemon.ts @@ -41,17 +41,7 @@ export const daemon: Command = { authorizationValiditySeconds: Number(authorizationValiditySeconds) }) - const info = await helia.info() - - stdout.write(`${info.agentVersion} is running\n`) - - if (info.multiaddrs.length > 0) { - stdout.write('Listening on:\n') - - info.multiaddrs.forEach(ma => { - stdout.write(` ${ma.toString()}\n`) - }) - } + stdout.write('Helia is running\n') fs.writeFileSync(lockfilePath, process.pid.toString()) } diff --git a/packages/cli/src/commands/gc.ts b/packages/cli/src/commands/gc.ts new file mode 100644 index 0000000..1b0243a --- /dev/null +++ b/packages/cli/src/commands/gc.ts @@ -0,0 +1,15 @@ +import type { Command, RootArgs } from '@helia/cli-utils' +import { findHelia } from '@helia/cli-utils/find-helia' + +export const gc: Command = { + command: 'gc', + description: 'Run garbage collection on the Helia node', + example: '$ helia gc', + async execute ({ directory, rpcAddress, stdout, user }) { + const { + helia + } = await findHelia(directory, rpcAddress, user) + + await helia.gc() + } +} diff --git a/packages/cli/src/commands/id.ts b/packages/cli/src/commands/id.ts deleted file mode 100644 index 7c61955..0000000 --- a/packages/cli/src/commands/id.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Command } from '@helia/cli-utils' - -interface IdArgs { - positionals?: string[] -} - -export const id: Command = { - command: 'id', - description: 'Print information out this Helia node', - example: '$ helia id', - async execute ({ helia, stdout }) { - const result = await helia.info() - - stdout.write(JSON.stringify(result, null, 2) + '\n') - } -} diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 022c9ab..b058cb2 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -1,12 +1,12 @@ import { init } from './init.js' import { daemon } from './daemon.js' -import { id } from './id.js' +import { gc } from './gc.js' import { status } from './status.js' import type { Command } from '@helia/cli-utils' export const commands: Array> = [ init, + gc, daemon, - id, status ] diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 6440077..1b2b980 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -2,7 +2,6 @@ import type { Command } from '@helia/cli-utils' import path from 'node:path' import fs from 'node:fs/promises' import { createEd25519PeerId, createRSAPeerId, createSecp256k1PeerId } from '@libp2p/peer-id-factory' -import { InvalidParametersError } from '@helia/interface/errors' import type { PeerId } from '@libp2p/interface-peer-id' import { logger } from '@libp2p/logger' import { FsDatastore } from 'datastore-fs' @@ -116,7 +115,7 @@ export const init: Command = { try { await fs.readdir(directory) // don't init if we are already inited - throw new InvalidParametersError(`Cowardly refusing to reinitialize Helia at ${directory}`) + throw new Error(`Cowardly refusing to reinitialize Helia at ${directory}`) } catch (err: any) { if (err.code !== 'ENOENT') { throw err @@ -128,7 +127,7 @@ export const init: Command = { try { await fs.access(configFilePath) // don't init if we are already inited - throw new InvalidParametersError(`Cowardly refusing to overwrite Helia config file at ${configFilePath}`) + throw new Error(`Cowardly refusing to overwrite Helia config file at ${configFilePath}`) } catch (err: any) { if (err.code !== 'ENOENT') { throw err @@ -138,7 +137,7 @@ export const init: Command = { const peerId = await generateKey(keyType, bits) if (peerId.publicKey == null || peerId.privateKey == null) { - throw new InvalidParametersError('Generated PeerId had missing components') + throw new Error('Generated PeerId had missing components') } log('create helia dir %s', directory) @@ -249,5 +248,5 @@ async function generateKey (type: string, bits: string = '2048'): Promise + +[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) +[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech) +[![codecov](https://img.shields.io/codecov/c/github/ipfs/helia-cli.svg?style=flat-square)](https://codecov.io/gh/ipfs/helia-cli) +[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/helia-cli/js-test-and-release.yml?branch=main\&style=flat-square)](https://github.com/ipfs/helia-cli/actions/workflows/js-test-and-release.yml?query=branch%3Amain) + +> Run ipns commands against a Helia node on the CLI + +## Table of contents + +- [Install](#install) +- [API Docs](#api-docs) +- [License](#license) +- [Contribute](#contribute) + +## Install + +```console +$ npm i @helia/ipns-cli +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribute + +Contributions welcome! Please check out [the issues](https://github.com/ipfs/helia-cli/issues). + +Also see our [contributing document](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) for more information on how we work, and about contributing in general. + +Please be aware that all interactions related to this repo are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) diff --git a/packages/ipns-cli/package.json b/packages/ipns-cli/package.json new file mode 100644 index 0000000..9f7c260 --- /dev/null +++ b/packages/ipns-cli/package.json @@ -0,0 +1,151 @@ +{ + "name": "@helia/ipns-cli", + "version": "0.0.0", + "description": "Run ipns commands against a Helia node on the CLI", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/ipfs/helia-cli/tree/master/packages/ipns-cli#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/ipfs/helia-cli.git" + }, + "bugs": { + "url": "https://github.com/ipfs/helia-cli/issues" + }, + "keywords": [ + "IPFS" + ], + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "bin": { + "ipns": "./dist/src/index.js" + }, + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "main" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "test": "aegir test", + "test:node": "aegir test -t node --cov", + "release": "aegir release" + }, + "dependencies": { + "@helia/cli-utils": "~0.0.0", + "@helia/ipns": "^1.0.1", + "@libp2p/interfaces": "^3.3.1", + "kleur": "^4.1.5", + "multiformats": "^11.0.1" + }, + "devDependencies": { + "aegir": "^38.1.0" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/ipns-cli/src/commands/index.ts b/packages/ipns-cli/src/commands/index.ts new file mode 100644 index 0000000..58c3a1d --- /dev/null +++ b/packages/ipns-cli/src/commands/index.ts @@ -0,0 +1,8 @@ +import type { Command } from '@helia/cli-utils' +import { publish } from './publish.js' +import { resolve } from './resolve.js' + +export const commands: Array> = [ + publish, + resolve +] diff --git a/packages/ipns-cli/src/commands/publish.ts b/packages/ipns-cli/src/commands/publish.ts new file mode 100644 index 0000000..bc79a54 --- /dev/null +++ b/packages/ipns-cli/src/commands/publish.ts @@ -0,0 +1,28 @@ +import { ipns } from '@helia/ipns' +import type { Command } from '@helia/cli-utils' +import { peerIdFromString } from '@libp2p/peer-id' +import { CID } from 'multiformats/cid' + +interface PublishArgs { + positionals: string[] +} + +export const publish: Command = { + command: 'publish', + description: 'Publish a CID as an IPNS name', + example: '$ ipns publish QmFoo 12D3Foo', + async execute ({ positionals, helia, stdout }) { + if (positionals == null || positionals.length !== 2) { + throw new TypeError('Missing positionals') + } + + const name = ipns(helia) + + const key = peerIdFromString(positionals[0]) + const cid = CID.parse(positionals[1]) + + const entry = await name.publish(key, cid) + + stdout.write(entry.toString()) + } +} diff --git a/packages/ipns-cli/src/commands/resolve.ts b/packages/ipns-cli/src/commands/resolve.ts new file mode 100644 index 0000000..bd5e212 --- /dev/null +++ b/packages/ipns-cli/src/commands/resolve.ts @@ -0,0 +1,26 @@ +import { ipns } from '@helia/ipns' +import type { Command } from '@helia/cli-utils' +import { peerIdFromString } from '@libp2p/peer-id' + +interface ResolveArgs { + positionals?: string[] + offset?: string + length?: string +} + +export const resolve: Command = { + command: 'resolve', + description: 'Resolve an IPNS name', + example: '$ ipns resolve ', + async execute ({ positionals, offset, length, helia, stdout }) { + if (positionals == null || positionals.length === 0) { + throw new TypeError('Missing positionals') + } + + const name = ipns(helia) + + const cid = await name.resolve(peerIdFromString(positionals[0])) + + stdout.write(cid.toString()) + } +} diff --git a/packages/ipns-cli/src/index.ts b/packages/ipns-cli/src/index.ts new file mode 100644 index 0000000..2ce98cd --- /dev/null +++ b/packages/ipns-cli/src/index.ts @@ -0,0 +1,18 @@ +#! /usr/bin/env node --trace-warnings +/* eslint-disable no-console */ + +import { cli } from '@helia/cli-utils' +import kleur from 'kleur' +import { commands } from './commands/index.js' + +async function main (): Promise { + const command = 'unixfs' + const description = `Run unixfs commands against a ${kleur.bold('Helia')} node` + + await cli(command, description, commands) +} + +main().catch(err => { + console.error(err) // eslint-disable-line no-console + process.exit(1) +}) diff --git a/packages/ipns-cli/test/index.spec.ts b/packages/ipns-cli/test/index.spec.ts new file mode 100644 index 0000000..e67dc48 --- /dev/null +++ b/packages/ipns-cli/test/index.spec.ts @@ -0,0 +1,7 @@ +import { expect } from 'aegir/chai' + +describe('cli', () => { + it('should start a node', () => { + expect(true).to.be.ok() + }) +}) diff --git a/packages/ipns-cli/tsconfig.json b/packages/ipns-cli/tsconfig.json new file mode 100644 index 0000000..b2ff54e --- /dev/null +++ b/packages/ipns-cli/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../cli-utils" + } + ] +} diff --git a/packages/rpc-client/package.json b/packages/rpc-client/package.json index 36be127..1c5e0bb 100644 --- a/packages/rpc-client/package.json +++ b/packages/rpc-client/package.json @@ -143,10 +143,10 @@ "@libp2p/interface-libp2p": "^1.1.0", "@libp2p/logger": "^2.0.5", "@libp2p/peer-id": "^2.0.0", - "@multiformats/multiaddr": "^11.1.5", - "interface-blockstore": "^4.0.1", + "@multiformats/multiaddr": "^12.1.0", + "interface-blockstore": "^5.0.0", "it-first": "^2.0.0", - "it-pb-stream": "^2.0.3", + "it-pb-stream": "^3.2.0", "multiformats": "^11.0.1" }, "devDependencies": { diff --git a/packages/rpc-client/src/commands/blockstore/close.ts b/packages/rpc-client/src/commands/blockstore/close.ts deleted file mode 100644 index eea558e..0000000 --- a/packages/rpc-client/src/commands/blockstore/close.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Helia } from '@helia/interface' -import type { HeliaRpcMethodConfig } from '../../index.js' -import { unaryCall } from '../utils/rpc-call.js' -import { CloseOptions } from '@helia/rpc-protocol/blockstore' - -export function createBlockstoreClose (config: HeliaRpcMethodConfig): Helia['blockstore']['close'] { - return unaryCall({ - resource: '/blockstore/close', - optionsCodec: CloseOptions - })(config) -} diff --git a/packages/rpc-client/src/commands/blockstore/get-all.ts b/packages/rpc-client/src/commands/blockstore/get-all.ts new file mode 100644 index 0000000..123028a --- /dev/null +++ b/packages/rpc-client/src/commands/blockstore/get-all.ts @@ -0,0 +1,25 @@ +import { GetAllOptions, GetAllRequest, GetAllResponse } from '@helia/rpc-protocol/blockstore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import { CID } from 'multiformats/cid' +import { streamingCall } from '../utils/rpc-call.js' + +export function createBlockstoreGetAll (config: HeliaRpcMethodConfig): Helia['blockstore']['getAll'] { + return streamingCall({ + resource: '/blockstore/get-all', + optionsCodec: GetAllOptions, + transformInput: (cid: CID) => { + return { + cid: cid.bytes + } + }, + inputCodec: GetAllRequest, + outputCodec: GetAllResponse, + transformOutput: (obj) => { + return { + cid: CID.decode(obj.cid), + block: obj.block + } + } + })(config) +} diff --git a/packages/rpc-client/src/commands/blockstore/open.ts b/packages/rpc-client/src/commands/blockstore/open.ts deleted file mode 100644 index a947303..0000000 --- a/packages/rpc-client/src/commands/blockstore/open.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Helia } from '@helia/interface' -import type { HeliaRpcMethodConfig } from '../../index.js' -import { unaryCall } from '../utils/rpc-call.js' -import { OpenOptions } from '@helia/rpc-protocol/blockstore' - -export function createBlockstoreOpen (config: HeliaRpcMethodConfig): Helia['blockstore']['open'] { - return unaryCall({ - resource: '/blockstore/open', - optionsCodec: OpenOptions - })(config) -} diff --git a/packages/rpc-client/src/commands/blockstore/put-many.ts b/packages/rpc-client/src/commands/blockstore/put-many.ts index 9029c31..aedb0cb 100644 --- a/packages/rpc-client/src/commands/blockstore/put-many.ts +++ b/packages/rpc-client/src/commands/blockstore/put-many.ts @@ -10,8 +10,8 @@ export function createBlockstorePutMany (config: HeliaRpcMethodConfig): Helia['b optionsCodec: PutManyOptions, transformInput: (pair: Pair) => { return { - cid: pair.key.bytes, - block: pair.value + cid: pair.cid.bytes, + block: pair.block } }, inputCodec: PutManyRequest, diff --git a/packages/rpc-client/src/commands/blockstore/put.ts b/packages/rpc-client/src/commands/blockstore/put.ts index 704912a..d88bda9 100644 --- a/packages/rpc-client/src/commands/blockstore/put.ts +++ b/packages/rpc-client/src/commands/blockstore/put.ts @@ -10,8 +10,8 @@ export function createBlockstorePut (config: HeliaRpcMethodConfig): Helia['block optionsCodec: PutOptions, transformInput: (pair: Pair): PutRequest => { return { - cid: pair.key.bytes, - block: pair.value + cid: pair.cid.bytes, + block: pair.block } }, inputCodec: PutRequest diff --git a/packages/rpc-client/src/commands/blockstore/batch.ts b/packages/rpc-client/src/commands/datastore/batch.ts similarity index 81% rename from packages/rpc-client/src/commands/blockstore/batch.ts rename to packages/rpc-client/src/commands/datastore/batch.ts index b7a753e..7022965 100644 --- a/packages/rpc-client/src/commands/blockstore/batch.ts +++ b/packages/rpc-client/src/commands/datastore/batch.ts @@ -1,16 +1,16 @@ -import { BatchOptions, BatchRequest, BatchRequestDelete, BatchRequestPut, BatchRequestType } from '@helia/rpc-protocol/blockstore' +import { BatchOptions, BatchRequest, BatchRequestDelete, BatchRequestPut, BatchRequestType } from '@helia/rpc-protocol/datastore' import type { Helia } from '@helia/interface' import type { HeliaRpcMethodConfig } from '../../index.js' -import type { CID } from 'multiformats/cid' +import type { Key } from 'interface-datastore' import { RPCCallMessage, RPCCallRequest, RPCCallMessageType } from '@helia/rpc-protocol/rpc' import { HELIA_RPC_PROTOCOL } from '@helia/rpc-protocol' import { pbStream } from 'it-pb-stream' -import type { Pair, Batch } from 'interface-blockstore' +import type { Pair, Batch } from 'interface-datastore' -export function createBlockstoreBatch (config: HeliaRpcMethodConfig): Helia['blockstore']['batch'] { +export function createDatastoreBatch (config: HeliaRpcMethodConfig): Helia['datastore']['batch'] { const batch = (): Batch => { let puts: Pair[] = [] - let dels: CID[] = [] + let dels: Key[] = [] const batch: Batch = { put (key, value) { @@ -28,7 +28,7 @@ export function createBlockstoreBatch (config: HeliaRpcMethodConfig): Helia['blo const stream = pbStream(duplex) stream.writePB({ - resource: '/blockstore/batch', + resource: '/datastore/batch', method: 'INVOKE', authorization: config.authorization, options: BatchOptions.encode({ @@ -42,8 +42,8 @@ export function createBlockstoreBatch (config: HeliaRpcMethodConfig): Helia['blo message: BatchRequest.encode({ type: BatchRequestType.BATCH_REQUEST_PUT, message: BatchRequestPut.encode({ - cid: key.bytes, - block: value + key: key.toString(), + value }) }) }, RPCCallMessage) @@ -51,13 +51,13 @@ export function createBlockstoreBatch (config: HeliaRpcMethodConfig): Helia['blo puts = [] - for (const cid of dels) { + for (const key of dels) { stream.writePB({ type: RPCCallMessageType.RPC_CALL_MESSAGE, message: BatchRequest.encode({ type: BatchRequestType.BATCH_REQUEST_DELETE, message: BatchRequestDelete.encode({ - cid: cid.bytes + key: key.toString() }) }) }, RPCCallMessage) diff --git a/packages/rpc-client/src/commands/datastore/delete-many.ts b/packages/rpc-client/src/commands/datastore/delete-many.ts new file mode 100644 index 0000000..c665822 --- /dev/null +++ b/packages/rpc-client/src/commands/datastore/delete-many.ts @@ -0,0 +1,22 @@ +import { DeleteManyOptions, DeleteManyRequest, DeleteManyResponse } from '@helia/rpc-protocol/datastore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import { Key } from 'interface-datastore' +import { streamingCall } from '../utils/rpc-call.js' + +export function createDatastoreDeleteMany (config: HeliaRpcMethodConfig): Helia['datastore']['deleteMany'] { + return streamingCall({ + resource: '/datastore/delete-many', + optionsCodec: DeleteManyOptions, + transformInput: (key: Key) => { + return { + key: key.toString() + } + }, + inputCodec: DeleteManyRequest, + outputCodec: DeleteManyResponse, + transformOutput: (obj: DeleteManyResponse) => { + return new Key(obj.key) + } + })(config) +} diff --git a/packages/rpc-client/src/commands/datastore/delete.ts b/packages/rpc-client/src/commands/datastore/delete.ts new file mode 100644 index 0000000..0802bff --- /dev/null +++ b/packages/rpc-client/src/commands/datastore/delete.ts @@ -0,0 +1,18 @@ +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import type { Key } from 'interface-datastore' +import { unaryCall } from '../utils/rpc-call.js' +import { DeleteOptions, DeleteRequest } from '@helia/rpc-protocol/datastore' + +export function createDatastoreDelete (config: HeliaRpcMethodConfig): Helia['datastore']['delete'] { + return unaryCall({ + resource: '/datastore/delete', + optionsCodec: DeleteOptions, + transformInput: (key: Key) => { + return { + key: key.toString() + } + }, + inputCodec: DeleteRequest + })(config) +} diff --git a/packages/rpc-client/src/commands/datastore/get-many.ts b/packages/rpc-client/src/commands/datastore/get-many.ts new file mode 100644 index 0000000..833997b --- /dev/null +++ b/packages/rpc-client/src/commands/datastore/get-many.ts @@ -0,0 +1,22 @@ +import { GetManyOptions, GetManyRequest, GetManyResponse } from '@helia/rpc-protocol/datastore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import type { Key } from 'interface-datastore' +import { streamingCall } from '../utils/rpc-call.js' + +export function createDatastoreGetMany (config: HeliaRpcMethodConfig): Helia['datastore']['getMany'] { + return streamingCall({ + resource: '/datastore/get-many', + optionsCodec: GetManyOptions, + transformInput: (key: Key) => { + return { + key: key.toString() + } + }, + inputCodec: GetManyRequest, + outputCodec: GetManyResponse, + transformOutput: (obj) => { + return obj.value + } + })(config) +} diff --git a/packages/rpc-client/src/commands/datastore/get.ts b/packages/rpc-client/src/commands/datastore/get.ts new file mode 100644 index 0000000..7cc91a9 --- /dev/null +++ b/packages/rpc-client/src/commands/datastore/get.ts @@ -0,0 +1,32 @@ +import { GetOptions, GetRequest, GetResponse, GetResponseType } from '@helia/rpc-protocol/datastore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import type { Key } from 'interface-datastore' +import { unaryCall } from '../utils/rpc-call.js' +import { CustomProgressEvent } from 'progress-events' + +export function createDatastoreGet (config: HeliaRpcMethodConfig): Helia['datastore']['get'] { + return unaryCall({ + resource: '/datastore/get', + optionsCodec: GetOptions, + transformInput: (key: Key) => { + return { + key: key.toString() + } + }, + inputCodec: GetRequest, + outputCodec: GetResponse, + transformOutput: (obj, onProgress): Uint8Array | undefined => { + if (obj.type === GetResponseType.GET_RESULT) { + return obj.value + } + + if (obj.type === GetResponseType.GET_PROGRESS && obj.progressEventType != null && onProgress != null) { + // todo: decode progress event CBOR + + const event = new CustomProgressEvent(obj.progressEventType) + onProgress(event) + } + } + })(config) +} diff --git a/packages/rpc-client/src/commands/datastore/has.ts b/packages/rpc-client/src/commands/datastore/has.ts new file mode 100644 index 0000000..b6758c0 --- /dev/null +++ b/packages/rpc-client/src/commands/datastore/has.ts @@ -0,0 +1,22 @@ +import { HasOptions, HasRequest, HasResponse } from '@helia/rpc-protocol/datastore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import type { Key } from 'interface-datastore' +import { unaryCall } from '../utils/rpc-call.js' + +export function createDatastoreHas (config: HeliaRpcMethodConfig): Helia['datastore']['has'] { + return unaryCall({ + resource: '/datastore/has', + optionsCodec: HasOptions, + transformInput: (key: Key) => { + return { + key: key.toString() + } + }, + inputCodec: HasRequest, + outputCodec: HasResponse, + transformOutput: (obj) => { + return obj.has + } + })(config) +} diff --git a/packages/rpc-client/src/commands/datastore/put-many.ts b/packages/rpc-client/src/commands/datastore/put-many.ts new file mode 100644 index 0000000..199ad73 --- /dev/null +++ b/packages/rpc-client/src/commands/datastore/put-many.ts @@ -0,0 +1,23 @@ +import { PutManyOptions, PutManyRequest, PutManyResponse } from '@helia/rpc-protocol/datastore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import { streamingCall } from '../utils/rpc-call.js' +import type { Pair } from 'interface-datastore' + +export function createDatastorePutMany (config: HeliaRpcMethodConfig): Helia['datastore']['putMany'] { + return streamingCall({ + resource: '/datastore/put-many', + optionsCodec: PutManyOptions, + transformInput: (pair: Pair) => { + return { + key: pair.key.toString(), + value: pair.value + } + }, + inputCodec: PutManyRequest, + outputCodec: PutManyResponse, + transformOutput: (obj): Uint8Array => { + return obj.value + } + })(config) +} diff --git a/packages/rpc-client/src/commands/datastore/put.ts b/packages/rpc-client/src/commands/datastore/put.ts new file mode 100644 index 0000000..559fe37 --- /dev/null +++ b/packages/rpc-client/src/commands/datastore/put.ts @@ -0,0 +1,19 @@ +import { PutOptions, PutRequest } from '@helia/rpc-protocol/datastore' +import type { Helia } from '@helia/interface' +import type { HeliaRpcMethodConfig } from '../../index.js' +import type { Pair } from 'interface-datastore' +import { unaryCall } from '../utils/rpc-call.js' + +export function createDatastorePut (config: HeliaRpcMethodConfig): Helia['datastore']['put'] { + return unaryCall({ + resource: '/datastore/put', + optionsCodec: PutOptions, + transformInput: (pair: Pair): PutRequest => { + return { + key: pair.key.toString(), + value: pair.value + } + }, + inputCodec: PutRequest + })(config) +} diff --git a/packages/rpc-client/src/commands/blockstore/query-keys.ts b/packages/rpc-client/src/commands/datastore/query-keys.ts similarity index 63% rename from packages/rpc-client/src/commands/blockstore/query-keys.ts rename to packages/rpc-client/src/commands/datastore/query-keys.ts index 1ed75c5..859329f 100644 --- a/packages/rpc-client/src/commands/blockstore/query-keys.ts +++ b/packages/rpc-client/src/commands/datastore/query-keys.ts @@ -1,16 +1,16 @@ import type { Helia } from '@helia/interface' import type { HeliaRpcMethodConfig } from '../../index.js' -import { CID } from 'multiformats/cid' -import { QueryKeysOptions, QueryKeysRequest, QueryKeysResponse } from '@helia/rpc-protocol/blockstore' +import { Key } from 'interface-datastore' +import { QueryKeysOptions, QueryKeysRequest, QueryKeysResponse } from '@helia/rpc-protocol/datastore' import { streamingCall } from '../utils/rpc-call.js' -export function createBlockstoreQueryKeys (config: HeliaRpcMethodConfig): Helia['blockstore']['queryKeys'] { +export function createDatastoreQueryKeys (config: HeliaRpcMethodConfig): Helia['datastore']['queryKeys'] { return streamingCall({ - resource: '/blockstore/query-keys', + resource: '/datastore/query-keys', optionsCodec: QueryKeysOptions, outputCodec: QueryKeysResponse, transformOutput: (obj: QueryKeysResponse) => { - return CID.decode(obj.key) + return new Key(obj.key) } })(config) } diff --git a/packages/rpc-client/src/commands/blockstore/query.ts b/packages/rpc-client/src/commands/datastore/query.ts similarity index 66% rename from packages/rpc-client/src/commands/blockstore/query.ts rename to packages/rpc-client/src/commands/datastore/query.ts index 43277a4..49e090b 100644 --- a/packages/rpc-client/src/commands/blockstore/query.ts +++ b/packages/rpc-client/src/commands/datastore/query.ts @@ -1,17 +1,17 @@ import type { Helia } from '@helia/interface' import type { HeliaRpcMethodConfig } from '../../index.js' -import { CID } from 'multiformats/cid' -import { QueryOptions, QueryRequest, QueryResponse } from '@helia/rpc-protocol/blockstore' +import { Key } from 'interface-datastore' +import { QueryOptions, QueryRequest, QueryResponse } from '@helia/rpc-protocol/datastore' import { streamingCall } from '../utils/rpc-call.js' -export function createBlockstoreQuery (config: HeliaRpcMethodConfig): Helia['blockstore']['query'] { +export function createDatastoreQuery (config: HeliaRpcMethodConfig): Helia['datastore']['query'] { return streamingCall({ - resource: '/blockstore/query', + resource: '/datastore/query', optionsCodec: QueryOptions, outputCodec: QueryResponse, transformOutput: (obj: QueryResponse) => { return { - key: CID.decode(obj.key), + key: new Key(obj.key), value: obj.value } } diff --git a/packages/rpc-client/src/commands/info.ts b/packages/rpc-client/src/commands/info.ts deleted file mode 100644 index 54bb0e1..0000000 --- a/packages/rpc-client/src/commands/info.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { multiaddr } from '@multiformats/multiaddr' -import { InfoOptions, InfoResponse } from '@helia/rpc-protocol/root' -import type { Helia } from '@helia/interface' -import { peerIdFromString } from '@libp2p/peer-id' -import type { HeliaRpcMethodConfig } from '../index.js' -import { unaryCall } from './utils/rpc-call.js' - -export function createInfo (config: HeliaRpcMethodConfig): Helia['info'] { - return unaryCall({ - resource: '/info', - optionsCodec: InfoOptions, - transformOptions: (obj) => { - return { - ...obj, - peerId: obj.peerId != null ? obj.peerId.toString() : undefined - } - }, - outputCodec: InfoResponse, - transformOutput: (obj) => { - return { - ...obj, - peerId: peerIdFromString(obj.peerId), - multiaddrs: obj.multiaddrs.map(str => multiaddr(str)) - } - } - })(config) -} diff --git a/packages/rpc-client/src/commands/utils/rpc-call.ts b/packages/rpc-client/src/commands/utils/rpc-call.ts index 5082d78..f898d8b 100644 --- a/packages/rpc-client/src/commands/utils/rpc-call.ts +++ b/packages/rpc-client/src/commands/utils/rpc-call.ts @@ -21,7 +21,7 @@ export interface CallOptions { transformInput?: (obj: any) => Request inputCodec?: Codec outputCodec?: Codec - transformOutput?: (obj: Response) => any + transformOutput?: (obj: Response, onProgress?: (evt: any) => void) => any } export function streamingCall (opts: CallOptions): (config: HeliaRpcMethodConfig) => any { @@ -79,7 +79,9 @@ export function streamingCall ( message = opts.transformOutput(message) } - yield message + if (message != null) { + yield message + } } continue case RPCCallMessageType.RPC_CALL_PROGRESS: diff --git a/packages/rpc-client/src/index.ts b/packages/rpc-client/src/index.ts index 8feb322..7f6c4e8 100644 --- a/packages/rpc-client/src/index.ts +++ b/packages/rpc-client/src/index.ts @@ -1,20 +1,25 @@ import type { Helia } from '@helia/interface' -import { createInfo } from './commands/info.js' import type { Libp2p } from '@libp2p/interface-libp2p' import type { Multiaddr } from '@multiformats/multiaddr' +import { createAuthorizationGet } from './commands/authorization/get.js' import { createBlockstoreDelete } from './commands/blockstore/delete.js' import { createBlockstoreGet } from './commands/blockstore/get.js' import { createBlockstoreHas } from './commands/blockstore/has.js' import { createBlockstorePut } from './commands/blockstore/put.js' -import { createAuthorizationGet } from './commands/authorization/get.js' import { createBlockstoreDeleteMany } from './commands/blockstore/delete-many.js' +import { createBlockstoreGetAll } from './commands/blockstore/get-all.js' import { createBlockstoreGetMany } from './commands/blockstore/get-many.js' import { createBlockstorePutMany } from './commands/blockstore/put-many.js' -import { createBlockstoreClose } from './commands/blockstore/close.js' -import { createBlockstoreOpen } from './commands/blockstore/open.js' -import { createBlockstoreBatch } from './commands/blockstore/batch.js' -import { createBlockstoreQueryKeys } from './commands/blockstore/query-keys.js' -import { createBlockstoreQuery } from './commands/blockstore/query.js' +import { createDatastoreDelete } from './commands/datastore/delete.js' +import { createDatastoreGet } from './commands/datastore/get.js' +import { createDatastoreHas } from './commands/datastore/has.js' +import { createDatastorePut } from './commands/datastore/put.js' +import { createDatastoreDeleteMany } from './commands/datastore/delete-many.js' +import { createDatastoreGetMany } from './commands/datastore/get-many.js' +import { createDatastorePutMany } from './commands/datastore/put-many.js' +import { createDatastoreBatch } from './commands/datastore/batch.js' +import { createDatastoreQueryKeys } from './commands/datastore/query-keys.js' +import { createDatastoreQuery } from './commands/datastore/query.js' export interface HeliaRpcClientConfig { multiaddr: Multiaddr @@ -39,24 +44,27 @@ export async function createHeliaRpcClient (config: HeliaRpcClientConfig): Promi } return { - info: createInfo(methodConfig), blockstore: { - batch: createBlockstoreBatch(methodConfig), - close: createBlockstoreClose(methodConfig), deleteMany: createBlockstoreDeleteMany(methodConfig), delete: createBlockstoreDelete(methodConfig), + getAll: createBlockstoreGetAll(methodConfig), getMany: createBlockstoreGetMany(methodConfig), get: createBlockstoreGet(methodConfig), has: createBlockstoreHas(methodConfig), - open: createBlockstoreOpen(methodConfig), putMany: createBlockstorePutMany(methodConfig), - put: createBlockstorePut(methodConfig), - queryKeys: createBlockstoreQueryKeys(methodConfig), - query: createBlockstoreQuery(methodConfig) + put: createBlockstorePut(methodConfig) }, - // @ts-expect-error incomplete implementation datastore: { - + batch: createDatastoreBatch(methodConfig), + deleteMany: createDatastoreDeleteMany(methodConfig), + delete: createDatastoreDelete(methodConfig), + getMany: createDatastoreGetMany(methodConfig), + get: createDatastoreGet(methodConfig), + has: createDatastoreHas(methodConfig), + putMany: createDatastorePutMany(methodConfig), + put: createDatastorePut(methodConfig), + queryKeys: createDatastoreQueryKeys(methodConfig), + query: createDatastoreQuery(methodConfig) }, // @ts-expect-error incomplete implementation libp2p: { diff --git a/packages/rpc-protocol/src/authorization.ts b/packages/rpc-protocol/src/authorization.ts index 2f839d6..d06abb1 100644 --- a/packages/rpc-protocol/src/authorization.ts +++ b/packages/rpc-protocol/src/authorization.ts @@ -68,9 +68,9 @@ export namespace GetRequest { w.fork() } - if (opts.writeDefaults === true || (obj.user != null && obj.user !== '')) { + if ((obj.user != null && obj.user !== '')) { w.uint32(10) - w.string(obj.user ?? '') + w.string(obj.user) } if (opts.lengthDelimited !== false) { @@ -126,9 +126,9 @@ export namespace GetResponse { w.fork() } - if (opts.writeDefaults === true || (obj.authorization != null && obj.authorization !== '')) { + if ((obj.authorization != null && obj.authorization !== '')) { w.uint32(10) - w.string(obj.authorization ?? '') + w.string(obj.authorization) } if (opts.lengthDelimited !== false) { diff --git a/packages/rpc-protocol/src/blockstore.proto b/packages/rpc-protocol/src/blockstore.proto index 0ed107b..22a0bc5 100644 --- a/packages/rpc-protocol/src/blockstore.proto +++ b/packages/rpc-protocol/src/blockstore.proto @@ -5,30 +5,6 @@ message Pair { bytes block = 2; } -message OpenOptions { - -} - -message OpenRequest { - -} - -message OpenResponse { - -} - -message CloseOptions { - -} - -message CloseRequest { - -} - -message CloseResponse { - -} - message PutOptions { } @@ -92,79 +68,39 @@ message PutManyResponse { bytes block = 2; } -message GetManyOptions { +message GetAllOptions { } -message GetManyRequest { - bytes cid = 1; -} +message GetAllRequest { -message GetManyResponse { - bytes block = 1; } -message DeleteManyOptions { - -} - -message DeleteManyRequest { - bytes cid = 1; -} - -message DeleteManyResponse { - bytes cid = 1; -} - -message BatchOptions { - -} - -enum BatchRequestType { - BATCH_REQUEST_PUT = 0; - BATCH_REQUEST_DELETE = 1; - BATCH_REQUEST_COMMIT = 2; -} - -message BatchRequest { - BatchRequestType type = 1; - bytes message = 2; -} - -message BatchRequestPut { +message GetAllResponse { bytes cid = 1; bytes block = 2; } -message BatchRequestDelete { - bytes cid = 1; -} - -message BatchResponse { - -} - -message QueryOptions { +message GetManyOptions { } -message QueryRequest { - +message GetManyRequest { + bytes cid = 1; } -message QueryResponse { - bytes key = 1; - bytes value = 2; +message GetManyResponse { + bytes block = 1; } -message QueryKeysOptions { +message DeleteManyOptions { } -message QueryKeysRequest { - +message DeleteManyRequest { + bytes cid = 1; } -message QueryKeysResponse { - bytes key = 1; +message DeleteManyResponse { + bytes cid = 1; } diff --git a/packages/rpc-protocol/src/blockstore.ts b/packages/rpc-protocol/src/blockstore.ts index 7b27753..888f59d 100644 --- a/packages/rpc-protocol/src/blockstore.ts +++ b/packages/rpc-protocol/src/blockstore.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime' +import { encodeMessage, decodeMessage, message } from 'protons-runtime' import type { Codec } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -23,14 +23,14 @@ export namespace Pair { w.fork() } - if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + if ((obj.cid != null && obj.cid.byteLength > 0)) { w.uint32(10) - w.bytes(obj.cid ?? new Uint8Array(0)) + w.bytes(obj.cid) } - if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { + if ((obj.block != null && obj.block.byteLength > 0)) { w.uint32(18) - w.bytes(obj.block ?? new Uint8Array(0)) + w.bytes(obj.block) } if (opts.lengthDelimited !== false) { @@ -76,282 +76,6 @@ export namespace Pair { } } -export interface OpenOptions {} - -export namespace OpenOptions { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, OpenOptions.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): OpenOptions => { - return decodeMessage(buf, OpenOptions.codec()) - } -} - -export interface OpenRequest {} - -export namespace OpenRequest { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, OpenRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): OpenRequest => { - return decodeMessage(buf, OpenRequest.codec()) - } -} - -export interface OpenResponse {} - -export namespace OpenResponse { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, OpenResponse.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): OpenResponse => { - return decodeMessage(buf, OpenResponse.codec()) - } -} - -export interface CloseOptions {} - -export namespace CloseOptions { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, CloseOptions.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): CloseOptions => { - return decodeMessage(buf, CloseOptions.codec()) - } -} - -export interface CloseRequest {} - -export namespace CloseRequest { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, CloseRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): CloseRequest => { - return decodeMessage(buf, CloseRequest.codec()) - } -} - -export interface CloseResponse {} - -export namespace CloseResponse { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, CloseResponse.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): CloseResponse => { - return decodeMessage(buf, CloseResponse.codec()) - } -} - export interface PutOptions {} export namespace PutOptions { @@ -413,14 +137,14 @@ export namespace PutRequest { w.fork() } - if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + if ((obj.cid != null && obj.cid.byteLength > 0)) { w.uint32(10) - w.bytes(obj.cid ?? new Uint8Array(0)) + w.bytes(obj.cid) } - if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { + if ((obj.block != null && obj.block.byteLength > 0)) { w.uint32(18) - w.bytes(obj.block ?? new Uint8Array(0)) + w.bytes(obj.block) } if (opts.lengthDelimited !== false) { @@ -572,9 +296,9 @@ export namespace GetRequest { w.fork() } - if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + if ((obj.cid != null && obj.cid.byteLength > 0)) { w.uint32(10) - w.bytes(obj.cid ?? new Uint8Array(0)) + w.bytes(obj.cid) } if (opts.lengthDelimited !== false) { @@ -630,9 +354,9 @@ export namespace GetResponse { w.fork() } - if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { + if ((obj.block != null && obj.block.byteLength > 0)) { w.uint32(18) - w.bytes(obj.block ?? new Uint8Array(0)) + w.bytes(obj.block) } if (opts.lengthDelimited !== false) { @@ -734,9 +458,9 @@ export namespace HasRequest { w.fork() } - if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + if ((obj.cid != null && obj.cid.byteLength > 0)) { w.uint32(10) - w.bytes(obj.cid ?? new Uint8Array(0)) + w.bytes(obj.cid) } if (opts.lengthDelimited !== false) { @@ -792,9 +516,9 @@ export namespace HasResponse { w.fork() } - if (opts.writeDefaults === true || (obj.has != null && obj.has !== false)) { + if ((obj.has != null && obj.has !== false)) { w.uint32(8) - w.bool(obj.has ?? false) + w.bool(obj.has) } if (opts.lengthDelimited !== false) { @@ -896,9 +620,9 @@ export namespace DeleteRequest { w.fork() } - if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + if ((obj.cid != null && obj.cid.byteLength > 0)) { w.uint32(10) - w.bytes(obj.cid ?? new Uint8Array(0)) + w.bytes(obj.cid) } if (opts.lengthDelimited !== false) { @@ -1047,14 +771,14 @@ export namespace PutManyRequest { w.fork() } - if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + if ((obj.cid != null && obj.cid.byteLength > 0)) { w.uint32(10) - w.bytes(obj.cid ?? new Uint8Array(0)) + w.bytes(obj.cid) } - if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { + if ((obj.block != null && obj.block.byteLength > 0)) { w.uint32(18) - w.bytes(obj.block ?? new Uint8Array(0)) + w.bytes(obj.block) } if (opts.lengthDelimited !== false) { @@ -1115,14 +839,14 @@ export namespace PutManyResponse { w.fork() } - if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + if ((obj.cid != null && obj.cid.byteLength > 0)) { w.uint32(10) - w.bytes(obj.cid ?? new Uint8Array(0)) + w.bytes(obj.cid) } - if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { + if ((obj.block != null && obj.block.byteLength > 0)) { w.uint32(18) - w.bytes(obj.block ?? new Uint8Array(0)) + w.bytes(obj.block) } if (opts.lengthDelimited !== false) { @@ -1168,14 +892,14 @@ export namespace PutManyResponse { } } -export interface GetManyOptions {} +export interface GetAllOptions {} -export namespace GetManyOptions { - let _codec: Codec +export namespace GetAllOptions { + let _codec: Codec - export const codec = (): Codec => { + export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, w, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { w.fork() } @@ -1205,41 +929,32 @@ export namespace GetManyOptions { return _codec } - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, GetManyOptions.codec()) + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetAllOptions.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): GetManyOptions => { - return decodeMessage(buf, GetManyOptions.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList): GetAllOptions => { + return decodeMessage(buf, GetAllOptions.codec()) } } -export interface GetManyRequest { - cid: Uint8Array -} +export interface GetAllRequest {} -export namespace GetManyRequest { - let _codec: Codec +export namespace GetAllRequest { + let _codec: Codec - export const codec = (): Codec => { + export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, w, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { w.fork() } - if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { - w.uint32(10) - w.bytes(obj.cid ?? new Uint8Array(0)) - } - if (opts.lengthDelimited !== false) { w.ldelim() } }, (reader, length) => { - const obj: any = { - cid: new Uint8Array(0) - } + const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -1247,9 +962,6 @@ export namespace GetManyRequest { const tag = reader.uint32() switch (tag >>> 3) { - case 1: - obj.cid = reader.bytes() - break default: reader.skipType(tag & 7) break @@ -1263,32 +975,38 @@ export namespace GetManyRequest { return _codec } - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, GetManyRequest.codec()) + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetAllRequest.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): GetManyRequest => { - return decodeMessage(buf, GetManyRequest.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList): GetAllRequest => { + return decodeMessage(buf, GetAllRequest.codec()) } } -export interface GetManyResponse { +export interface GetAllResponse { + cid: Uint8Array block: Uint8Array } -export namespace GetManyResponse { - let _codec: Codec +export namespace GetAllResponse { + let _codec: Codec - export const codec = (): Codec => { + export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, w, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { w.fork() } - if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { + if ((obj.cid != null && obj.cid.byteLength > 0)) { w.uint32(10) - w.bytes(obj.block ?? new Uint8Array(0)) + w.bytes(obj.cid) + } + + if ((obj.block != null && obj.block.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.block) } if (opts.lengthDelimited !== false) { @@ -1296,6 +1014,7 @@ export namespace GetManyResponse { } }, (reader, length) => { const obj: any = { + cid: new Uint8Array(0), block: new Uint8Array(0) } @@ -1306,6 +1025,9 @@ export namespace GetManyResponse { switch (tag >>> 3) { case 1: + obj.cid = reader.bytes() + break + case 2: obj.block = reader.bytes() break default: @@ -1321,23 +1043,23 @@ export namespace GetManyResponse { return _codec } - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, GetManyResponse.codec()) + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetAllResponse.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): GetManyResponse => { - return decodeMessage(buf, GetManyResponse.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList): GetAllResponse => { + return decodeMessage(buf, GetAllResponse.codec()) } } -export interface DeleteManyOptions {} +export interface GetManyOptions {} -export namespace DeleteManyOptions { - let _codec: Codec +export namespace GetManyOptions { + let _codec: Codec - export const codec = (): Codec => { + export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, w, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { w.fork() } @@ -1367,32 +1089,32 @@ export namespace DeleteManyOptions { return _codec } - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, DeleteManyOptions.codec()) + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetManyOptions.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteManyOptions => { - return decodeMessage(buf, DeleteManyOptions.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList): GetManyOptions => { + return decodeMessage(buf, GetManyOptions.codec()) } } -export interface DeleteManyRequest { +export interface GetManyRequest { cid: Uint8Array } -export namespace DeleteManyRequest { - let _codec: Codec +export namespace GetManyRequest { + let _codec: Codec - export const codec = (): Codec => { + export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, w, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { w.fork() } - if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + if ((obj.cid != null && obj.cid.byteLength > 0)) { w.uint32(10) - w.bytes(obj.cid ?? new Uint8Array(0)) + w.bytes(obj.cid) } if (opts.lengthDelimited !== false) { @@ -1425,32 +1147,32 @@ export namespace DeleteManyRequest { return _codec } - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, DeleteManyRequest.codec()) + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetManyRequest.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteManyRequest => { - return decodeMessage(buf, DeleteManyRequest.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList): GetManyRequest => { + return decodeMessage(buf, GetManyRequest.codec()) } } -export interface DeleteManyResponse { - cid: Uint8Array +export interface GetManyResponse { + block: Uint8Array } -export namespace DeleteManyResponse { - let _codec: Codec +export namespace GetManyResponse { + let _codec: Codec - export const codec = (): Codec => { + export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, w, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { w.fork() } - if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + if ((obj.block != null && obj.block.byteLength > 0)) { w.uint32(10) - w.bytes(obj.cid ?? new Uint8Array(0)) + w.bytes(obj.block) } if (opts.lengthDelimited !== false) { @@ -1458,7 +1180,7 @@ export namespace DeleteManyResponse { } }, (reader, length) => { const obj: any = { - cid: new Uint8Array(0) + block: new Uint8Array(0) } const end = length == null ? reader.len : reader.pos + length @@ -1468,7 +1190,7 @@ export namespace DeleteManyResponse { switch (tag >>> 3) { case 1: - obj.cid = reader.bytes() + obj.block = reader.bytes() break default: reader.skipType(tag & 7) @@ -1483,23 +1205,23 @@ export namespace DeleteManyResponse { return _codec } - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, DeleteManyResponse.codec()) + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GetManyResponse.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteManyResponse => { - return decodeMessage(buf, DeleteManyResponse.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList): GetManyResponse => { + return decodeMessage(buf, GetManyResponse.codec()) } } -export interface BatchOptions {} +export interface DeleteManyOptions {} -export namespace BatchOptions { - let _codec: Codec +export namespace DeleteManyOptions { + let _codec: Codec - export const codec = (): Codec => { + export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, w, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { w.fork() } @@ -1529,123 +1251,32 @@ export namespace BatchOptions { return _codec } - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, BatchOptions.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): BatchOptions => { - return decodeMessage(buf, BatchOptions.codec()) - } -} - -export enum BatchRequestType { - BATCH_REQUEST_PUT = 'BATCH_REQUEST_PUT', - BATCH_REQUEST_DELETE = 'BATCH_REQUEST_DELETE', - BATCH_REQUEST_COMMIT = 'BATCH_REQUEST_COMMIT' -} - -enum __BatchRequestTypeValues { - BATCH_REQUEST_PUT = 0, - BATCH_REQUEST_DELETE = 1, - BATCH_REQUEST_COMMIT = 2 -} - -export namespace BatchRequestType { - export const codec = (): Codec => { - return enumeration(__BatchRequestTypeValues) - } -} -export interface BatchRequest { - type: BatchRequestType - message: Uint8Array -} - -export namespace BatchRequest { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.writeDefaults === true || (obj.type != null && __BatchRequestTypeValues[obj.type] !== 0)) { - w.uint32(8) - BatchRequestType.codec().encode(obj.type ?? BatchRequestType.BATCH_REQUEST_PUT, w) - } - - if (opts.writeDefaults === true || (obj.message != null && obj.message.byteLength > 0)) { - w.uint32(18) - w.bytes(obj.message ?? new Uint8Array(0)) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - type: BatchRequestType.BATCH_REQUEST_PUT, - message: new Uint8Array(0) - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.type = BatchRequestType.codec().decode(reader) - break - case 2: - obj.message = reader.bytes() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, BatchRequest.codec()) + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteManyOptions.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): BatchRequest => { - return decodeMessage(buf, BatchRequest.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteManyOptions => { + return decodeMessage(buf, DeleteManyOptions.codec()) } } -export interface BatchRequestPut { +export interface DeleteManyRequest { cid: Uint8Array - block: Uint8Array } -export namespace BatchRequestPut { - let _codec: Codec +export namespace DeleteManyRequest { + let _codec: Codec - export const codec = (): Codec => { + export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, w, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { w.fork() } - if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + if ((obj.cid != null && obj.cid.byteLength > 0)) { w.uint32(10) - w.bytes(obj.cid ?? new Uint8Array(0)) - } - - if (opts.writeDefaults === true || (obj.block != null && obj.block.byteLength > 0)) { - w.uint32(18) - w.bytes(obj.block ?? new Uint8Array(0)) + w.bytes(obj.cid) } if (opts.lengthDelimited !== false) { @@ -1653,8 +1284,7 @@ export namespace BatchRequestPut { } }, (reader, length) => { const obj: any = { - cid: new Uint8Array(0), - block: new Uint8Array(0) + cid: new Uint8Array(0) } const end = length == null ? reader.len : reader.pos + length @@ -1666,9 +1296,6 @@ export namespace BatchRequestPut { case 1: obj.cid = reader.bytes() break - case 2: - obj.block = reader.bytes() - break default: reader.skipType(tag & 7) break @@ -1682,32 +1309,32 @@ export namespace BatchRequestPut { return _codec } - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, BatchRequestPut.codec()) + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteManyRequest.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): BatchRequestPut => { - return decodeMessage(buf, BatchRequestPut.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteManyRequest => { + return decodeMessage(buf, DeleteManyRequest.codec()) } } -export interface BatchRequestDelete { +export interface DeleteManyResponse { cid: Uint8Array } -export namespace BatchRequestDelete { - let _codec: Codec +export namespace DeleteManyResponse { + let _codec: Codec - export const codec = (): Codec => { + export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, w, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { w.fork() } - if (opts.writeDefaults === true || (obj.cid != null && obj.cid.byteLength > 0)) { + if ((obj.cid != null && obj.cid.byteLength > 0)) { w.uint32(10) - w.bytes(obj.cid ?? new Uint8Array(0)) + w.bytes(obj.cid) } if (opts.lengthDelimited !== false) { @@ -1740,367 +1367,11 @@ export namespace BatchRequestDelete { return _codec } - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, BatchRequestDelete.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): BatchRequestDelete => { - return decodeMessage(buf, BatchRequestDelete.codec()) - } -} - -export interface BatchResponse {} - -export namespace BatchResponse { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, BatchResponse.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): BatchResponse => { - return decodeMessage(buf, BatchResponse.codec()) - } -} - -export interface QueryOptions {} - -export namespace QueryOptions { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, QueryOptions.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): QueryOptions => { - return decodeMessage(buf, QueryOptions.codec()) - } -} - -export interface QueryRequest {} - -export namespace QueryRequest { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, QueryRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): QueryRequest => { - return decodeMessage(buf, QueryRequest.codec()) - } -} - -export interface QueryResponse { - key: Uint8Array - value: Uint8Array -} - -export namespace QueryResponse { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.writeDefaults === true || (obj.key != null && obj.key.byteLength > 0)) { - w.uint32(10) - w.bytes(obj.key ?? new Uint8Array(0)) - } - - if (opts.writeDefaults === true || (obj.value != null && obj.value.byteLength > 0)) { - w.uint32(18) - w.bytes(obj.value ?? new Uint8Array(0)) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - key: new Uint8Array(0), - value: new Uint8Array(0) - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.key = reader.bytes() - break - case 2: - obj.value = reader.bytes() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, QueryResponse.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): QueryResponse => { - return decodeMessage(buf, QueryResponse.codec()) - } -} - -export interface QueryKeysOptions {} - -export namespace QueryKeysOptions { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, QueryKeysOptions.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): QueryKeysOptions => { - return decodeMessage(buf, QueryKeysOptions.codec()) - } -} - -export interface QueryKeysRequest {} - -export namespace QueryKeysRequest { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, QueryKeysRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): QueryKeysRequest => { - return decodeMessage(buf, QueryKeysRequest.codec()) - } -} - -export interface QueryKeysResponse { - key: Uint8Array -} - -export namespace QueryKeysResponse { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.writeDefaults === true || (obj.key != null && obj.key.byteLength > 0)) { - w.uint32(10) - w.bytes(obj.key ?? new Uint8Array(0)) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - key: new Uint8Array(0) - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.key = reader.bytes() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, QueryKeysResponse.codec()) + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, DeleteManyResponse.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): QueryKeysResponse => { - return decodeMessage(buf, QueryKeysResponse.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList): DeleteManyResponse => { + return decodeMessage(buf, DeleteManyResponse.codec()) } } diff --git a/packages/rpc-protocol/src/datastore.proto b/packages/rpc-protocol/src/datastore.proto index 6f9c370..a9b0099 100644 --- a/packages/rpc-protocol/src/datastore.proto +++ b/packages/rpc-protocol/src/datastore.proto @@ -1,29 +1,5 @@ syntax = "proto3"; -message OpenOptions { - -} - -message OpenRequest { - -} - -message OpenResponse { - -} - -message CloseOptions { - -} - -message CloseRequest { - -} - -message CloseResponse { - -} - message PutOptions { } @@ -54,7 +30,7 @@ message GetResponse { GetResponseType type = 1; optional bytes value = 2; optional string progress_event_type = 3; - map progress_event_data = 4; + optional bytes progress_event_data = 4; } message HasOptions { @@ -131,12 +107,28 @@ message BatchOptions { } +enum BatchRequestType { + BATCH_REQUEST_PUT = 0; + BATCH_REQUEST_DELETE = 1; + BATCH_REQUEST_COMMIT = 2; +} + message BatchRequest { + BatchRequestType type = 1; + bytes message = 2; +} + +message BatchRequestPut { + string key = 1; + bytes value = 2; +} +message BatchRequestDelete { + string key = 1; } message BatchResponse { - string id = 1; + } message QueryOptions { @@ -148,7 +140,8 @@ message QueryRequest { } message QueryResponse { - + string key = 1; + bytes value = 2; } message QueryKeysOptions { @@ -160,5 +153,5 @@ message QueryKeysRequest { } message QueryKeysResponse { - + string key = 1; } diff --git a/packages/rpc-protocol/src/datastore.ts b/packages/rpc-protocol/src/datastore.ts index 46ae8d8..b9554a2 100644 --- a/packages/rpc-protocol/src/datastore.ts +++ b/packages/rpc-protocol/src/datastore.ts @@ -8,282 +8,6 @@ import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runt import type { Codec } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' -export interface OpenOptions {} - -export namespace OpenOptions { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, OpenOptions.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): OpenOptions => { - return decodeMessage(buf, OpenOptions.codec()) - } -} - -export interface OpenRequest {} - -export namespace OpenRequest { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, OpenRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): OpenRequest => { - return decodeMessage(buf, OpenRequest.codec()) - } -} - -export interface OpenResponse {} - -export namespace OpenResponse { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, OpenResponse.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): OpenResponse => { - return decodeMessage(buf, OpenResponse.codec()) - } -} - -export interface CloseOptions {} - -export namespace CloseOptions { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, CloseOptions.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): CloseOptions => { - return decodeMessage(buf, CloseOptions.codec()) - } -} - -export interface CloseRequest {} - -export namespace CloseRequest { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, CloseRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): CloseRequest => { - return decodeMessage(buf, CloseRequest.codec()) - } -} - -export interface CloseResponse {} - -export namespace CloseResponse { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, CloseResponse.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): CloseResponse => { - return decodeMessage(buf, CloseResponse.codec()) - } -} - export interface PutOptions {} export namespace PutOptions { @@ -345,14 +69,14 @@ export namespace PutRequest { w.fork() } - if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + if ((obj.key != null && obj.key !== '')) { w.uint32(10) - w.string(obj.key ?? '') + w.string(obj.key) } - if (opts.writeDefaults === true || (obj.value != null && obj.value.byteLength > 0)) { + if ((obj.value != null && obj.value.byteLength > 0)) { w.uint32(18) - w.bytes(obj.value ?? new Uint8Array(0)) + w.bytes(obj.value) } if (opts.lengthDelimited !== false) { @@ -504,9 +228,9 @@ export namespace GetRequest { w.fork() } - if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + if ((obj.key != null && obj.key !== '')) { w.uint32(10) - w.string(obj.key ?? '') + w.string(obj.key) } if (opts.lengthDelimited !== false) { @@ -567,78 +291,10 @@ export interface GetResponse { type: GetResponseType value?: Uint8Array progressEventType?: string - progressEventData: Map + progressEventData?: Uint8Array } export namespace GetResponse { - export interface GetResponse$progressEventDataEntry { - key: string - value: string - } - - export namespace GetResponse$progressEventDataEntry { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { - w.uint32(10) - w.string(obj.key ?? '') - } - - if (opts.writeDefaults === true || (obj.value != null && obj.value !== '')) { - w.uint32(18) - w.string(obj.value ?? '') - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - key: '', - value: '' - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.key = reader.string() - break - case 2: - obj.value = reader.string() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, GetResponse$progressEventDataEntry.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): GetResponse$progressEventDataEntry => { - return decodeMessage(buf, GetResponse$progressEventDataEntry.codec()) - } - } - let _codec: Codec export const codec = (): Codec => { @@ -648,9 +304,9 @@ export namespace GetResponse { w.fork() } - if (opts.writeDefaults === true || (obj.type != null && __GetResponseTypeValues[obj.type] !== 0)) { + if (obj.type != null && __GetResponseTypeValues[obj.type] !== 0) { w.uint32(8) - GetResponseType.codec().encode(obj.type ?? GetResponseType.GET_PROGRESS, w) + GetResponseType.codec().encode(obj.type, w) } if (obj.value != null) { @@ -663,13 +319,9 @@ export namespace GetResponse { w.string(obj.progressEventType) } - if (obj.progressEventData != null && obj.progressEventData.size !== 0) { - for (const [key, value] of obj.progressEventData.entries()) { - w.uint32(34) - GetResponse.GetResponse$progressEventDataEntry.codec().encode({ key, value }, w, { - writeDefaults: true - }) - } + if (obj.progressEventData != null) { + w.uint32(34) + w.bytes(obj.progressEventData) } if (opts.lengthDelimited !== false) { @@ -677,8 +329,7 @@ export namespace GetResponse { } }, (reader, length) => { const obj: any = { - type: GetResponseType.GET_PROGRESS, - progressEventData: new Map() + type: GetResponseType.GET_PROGRESS } const end = length == null ? reader.len : reader.pos + length @@ -696,11 +347,9 @@ export namespace GetResponse { case 3: obj.progressEventType = reader.string() break - case 4: { - const entry = GetResponse.GetResponse$progressEventDataEntry.codec().decode(reader, reader.uint32()) - obj.progressEventData.set(entry.key, entry.value) + case 4: + obj.progressEventData = reader.bytes() break - } default: reader.skipType(tag & 7) break @@ -783,9 +432,9 @@ export namespace HasRequest { w.fork() } - if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + if ((obj.key != null && obj.key !== '')) { w.uint32(10) - w.string(obj.key ?? '') + w.string(obj.key) } if (opts.lengthDelimited !== false) { @@ -841,9 +490,9 @@ export namespace HasResponse { w.fork() } - if (opts.writeDefaults === true || (obj.has != null && obj.has !== false)) { + if ((obj.has != null && obj.has !== false)) { w.uint32(8) - w.bool(obj.has ?? false) + w.bool(obj.has) } if (opts.lengthDelimited !== false) { @@ -945,9 +594,9 @@ export namespace DeleteRequest { w.fork() } - if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + if ((obj.key != null && obj.key !== '')) { w.uint32(10) - w.string(obj.key ?? '') + w.string(obj.key) } if (opts.lengthDelimited !== false) { @@ -1096,14 +745,14 @@ export namespace PutManyRequest { w.fork() } - if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + if ((obj.key != null && obj.key !== '')) { w.uint32(10) - w.string(obj.key ?? '') + w.string(obj.key) } - if (opts.writeDefaults === true || (obj.value != null && obj.value.byteLength > 0)) { + if ((obj.value != null && obj.value.byteLength > 0)) { w.uint32(18) - w.bytes(obj.value ?? new Uint8Array(0)) + w.bytes(obj.value) } if (opts.lengthDelimited !== false) { @@ -1164,14 +813,14 @@ export namespace PutManyResponse { w.fork() } - if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + if ((obj.key != null && obj.key !== '')) { w.uint32(10) - w.string(obj.key ?? '') + w.string(obj.key) } - if (opts.writeDefaults === true || (obj.value != null && obj.value.byteLength > 0)) { + if ((obj.value != null && obj.value.byteLength > 0)) { w.uint32(18) - w.bytes(obj.value ?? new Uint8Array(0)) + w.bytes(obj.value) } if (opts.lengthDelimited !== false) { @@ -1277,9 +926,9 @@ export namespace GetManyRequest { w.fork() } - if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + if ((obj.key != null && obj.key !== '')) { w.uint32(10) - w.string(obj.key ?? '') + w.string(obj.key) } if (opts.lengthDelimited !== false) { @@ -1359,14 +1008,14 @@ export namespace GetManyResponse { w.fork() } - if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + if ((obj.key != null && obj.key !== '')) { w.uint32(10) - w.string(obj.key ?? '') + w.string(obj.key) } - if (opts.writeDefaults === true || (obj.value != null && obj.value !== '')) { + if ((obj.value != null && obj.value !== '')) { w.uint32(18) - w.string(obj.value ?? '') + w.string(obj.value) } if (opts.lengthDelimited !== false) { @@ -1421,9 +1070,9 @@ export namespace GetManyResponse { w.fork() } - if (opts.writeDefaults === true || (obj.type != null && __GetManyResponseTypeValues[obj.type] !== 0)) { + if (obj.type != null && __GetManyResponseTypeValues[obj.type] !== 0) { w.uint32(8) - GetManyResponseType.codec().encode(obj.type ?? GetManyResponseType.GET_MANY_PROGRESS, w) + GetManyResponseType.codec().encode(obj.type, w) } if (obj.value != null) { @@ -1439,9 +1088,7 @@ export namespace GetManyResponse { if (obj.progressEventData != null && obj.progressEventData.size !== 0) { for (const [key, value] of obj.progressEventData.entries()) { w.uint32(34) - GetManyResponse.GetManyResponse$progressEventDataEntry.codec().encode({ key, value }, w, { - writeDefaults: true - }) + GetManyResponse.GetManyResponse$progressEventDataEntry.codec().encode({ key, value }, w) } } @@ -1556,9 +1203,9 @@ export namespace DeleteManyRequest { w.fork() } - if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + if ((obj.key != null && obj.key !== '')) { w.uint32(10) - w.string(obj.key ?? '') + w.string(obj.key) } if (opts.lengthDelimited !== false) { @@ -1614,9 +1261,9 @@ export namespace DeleteManyResponse { w.fork() } - if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + if ((obj.key != null && obj.key !== '')) { w.uint32(10) - w.string(obj.key ?? '') + w.string(obj.key) } if (opts.lengthDelimited !== false) { @@ -1704,7 +1351,27 @@ export namespace BatchOptions { } } -export interface BatchRequest {} +export enum BatchRequestType { + BATCH_REQUEST_PUT = 'BATCH_REQUEST_PUT', + BATCH_REQUEST_DELETE = 'BATCH_REQUEST_DELETE', + BATCH_REQUEST_COMMIT = 'BATCH_REQUEST_COMMIT' +} + +enum __BatchRequestTypeValues { + BATCH_REQUEST_PUT = 0, + BATCH_REQUEST_DELETE = 1, + BATCH_REQUEST_COMMIT = 2 +} + +export namespace BatchRequestType { + export const codec = (): Codec => { + return enumeration(__BatchRequestTypeValues) + } +} +export interface BatchRequest { + type: BatchRequestType + message: Uint8Array +} export namespace BatchRequest { let _codec: Codec @@ -1716,11 +1383,24 @@ export namespace BatchRequest { w.fork() } + if (obj.type != null && __BatchRequestTypeValues[obj.type] !== 0) { + w.uint32(8) + BatchRequestType.codec().encode(obj.type, w) + } + + if ((obj.message != null && obj.message.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.message) + } + if (opts.lengthDelimited !== false) { w.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + type: BatchRequestType.BATCH_REQUEST_PUT, + message: new Uint8Array(0) + } const end = length == null ? reader.len : reader.pos + length @@ -1728,6 +1408,12 @@ export namespace BatchRequest { const tag = reader.uint32() switch (tag >>> 3) { + case 1: + obj.type = BatchRequestType.codec().decode(reader) + break + case 2: + obj.message = reader.bytes() + break default: reader.skipType(tag & 7) break @@ -1750,23 +1436,29 @@ export namespace BatchRequest { } } -export interface BatchResponse { - id: string +export interface BatchRequestPut { + key: string + value: Uint8Array } -export namespace BatchResponse { - let _codec: Codec +export namespace BatchRequestPut { + let _codec: Codec - export const codec = (): Codec => { + export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, w, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { w.fork() } - if (opts.writeDefaults === true || (obj.id != null && obj.id !== '')) { + if ((obj.key != null && obj.key !== '')) { w.uint32(10) - w.string(obj.id ?? '') + w.string(obj.key) + } + + if ((obj.value != null && obj.value.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.value) } if (opts.lengthDelimited !== false) { @@ -1774,7 +1466,8 @@ export namespace BatchResponse { } }, (reader, length) => { const obj: any = { - id: '' + key: '', + value: new Uint8Array(0) } const end = length == null ? reader.len : reader.pos + length @@ -1784,7 +1477,10 @@ export namespace BatchResponse { switch (tag >>> 3) { case 1: - obj.id = reader.string() + obj.key = reader.string() + break + case 2: + obj.value = reader.bytes() break default: reader.skipType(tag & 7) @@ -1799,6 +1495,110 @@ export namespace BatchResponse { return _codec } + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, BatchRequestPut.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): BatchRequestPut => { + return decodeMessage(buf, BatchRequestPut.codec()) + } +} + +export interface BatchRequestDelete { + key: string +} + +export namespace BatchRequestDelete { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if ((obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, BatchRequestDelete.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): BatchRequestDelete => { + return decodeMessage(buf, BatchRequestDelete.codec()) + } +} + +export interface BatchResponse {} + +export namespace BatchResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + export const encode = (obj: Partial): Uint8Array => { return encodeMessage(obj, BatchResponse.codec()) } @@ -1900,7 +1700,10 @@ export namespace QueryRequest { } } -export interface QueryResponse {} +export interface QueryResponse { + key: string + value: Uint8Array +} export namespace QueryResponse { let _codec: Codec @@ -1912,11 +1715,24 @@ export namespace QueryResponse { w.fork() } + if ((obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key) + } + + if ((obj.value != null && obj.value.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.value) + } + if (opts.lengthDelimited !== false) { w.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + key: '', + value: new Uint8Array(0) + } const end = length == null ? reader.len : reader.pos + length @@ -1924,6 +1740,12 @@ export namespace QueryResponse { const tag = reader.uint32() switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + case 2: + obj.value = reader.bytes() + break default: reader.skipType(tag & 7) break @@ -2038,7 +1860,9 @@ export namespace QueryKeysRequest { } } -export interface QueryKeysResponse {} +export interface QueryKeysResponse { + key: string +} export namespace QueryKeysResponse { let _codec: Codec @@ -2050,11 +1874,18 @@ export namespace QueryKeysResponse { w.fork() } + if ((obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key) + } + if (opts.lengthDelimited !== false) { w.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + key: '' + } const end = length == null ? reader.len : reader.pos + length @@ -2062,6 +1893,9 @@ export namespace QueryKeysResponse { const tag = reader.uint32() switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break default: reader.skipType(tag & 7) break diff --git a/packages/rpc-protocol/src/pins.proto b/packages/rpc-protocol/src/pins.proto new file mode 100644 index 0000000..d75e6b9 --- /dev/null +++ b/packages/rpc-protocol/src/pins.proto @@ -0,0 +1,54 @@ +syntax = "proto3"; + +message PinAddOptions { + optional uint32 depth = 1; + map metadata = 2; +} + +message PinAddRequest { + +} + +message PinAddResponse { + bytes cid = 1; + uint32 depth = 2; + map metadata = 3; +} + +message PinRmOptions { + +} + +message PinRmRequest { + +} + +message PinRmResponse { + +} + +message PinLsOptions { + optional bytes cid = 1; +} + +message PinLsRequest { + +} + +message PinLsResponse { + bytes cid = 1; + uint32 depth = 2; + map metadata = 3; +} + +message IsPinnedOptions { + +} + +message IsPinnedRequest { + bytes cid = 1; +} + +message IsPinnedResponse { + bool pinned = 1; +} diff --git a/packages/rpc-protocol/src/pins.ts b/packages/rpc-protocol/src/pins.ts new file mode 100644 index 0000000..37456b6 --- /dev/null +++ b/packages/rpc-protocol/src/pins.ts @@ -0,0 +1,896 @@ +/* eslint-disable import/export */ +/* eslint-disable complexity */ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import { encodeMessage, decodeMessage, message } from 'protons-runtime' +import type { Codec } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' + +export interface PinAddOptions { + depth?: number + metadata: Map +} + +export namespace PinAddOptions { + export interface PinAddOptions$metadataEntry { + key: string + value: string + } + + export namespace PinAddOptions$metadataEntry { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if ((obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key) + } + + if ((obj.value != null && obj.value !== '')) { + w.uint32(18) + w.string(obj.value) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '', + value: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + case 2: + obj.value = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PinAddOptions$metadataEntry.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PinAddOptions$metadataEntry => { + return decodeMessage(buf, PinAddOptions$metadataEntry.codec()) + } + } + + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.depth != null) { + w.uint32(8) + w.uint32(obj.depth) + } + + if (obj.metadata != null && obj.metadata.size !== 0) { + for (const [key, value] of obj.metadata.entries()) { + w.uint32(18) + PinAddOptions.PinAddOptions$metadataEntry.codec().encode({ key, value }, w) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + metadata: new Map() + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.depth = reader.uint32() + break + case 2: { + const entry = PinAddOptions.PinAddOptions$metadataEntry.codec().decode(reader, reader.uint32()) + obj.metadata.set(entry.key, entry.value) + break + } + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PinAddOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PinAddOptions => { + return decodeMessage(buf, PinAddOptions.codec()) + } +} + +export interface PinAddRequest {} + +export namespace PinAddRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PinAddRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PinAddRequest => { + return decodeMessage(buf, PinAddRequest.codec()) + } +} + +export interface PinAddResponse { + cid: Uint8Array + depth: number + metadata: Map +} + +export namespace PinAddResponse { + export interface PinAddResponse$metadataEntry { + key: string + value: string + } + + export namespace PinAddResponse$metadataEntry { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if ((obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key) + } + + if ((obj.value != null && obj.value !== '')) { + w.uint32(18) + w.string(obj.value) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '', + value: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + case 2: + obj.value = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PinAddResponse$metadataEntry.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PinAddResponse$metadataEntry => { + return decodeMessage(buf, PinAddResponse$metadataEntry.codec()) + } + } + + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if ((obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid) + } + + if ((obj.depth != null && obj.depth !== 0)) { + w.uint32(16) + w.uint32(obj.depth) + } + + if (obj.metadata != null && obj.metadata.size !== 0) { + for (const [key, value] of obj.metadata.entries()) { + w.uint32(26) + PinAddResponse.PinAddResponse$metadataEntry.codec().encode({ key, value }, w) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0), + depth: 0, + metadata: new Map() + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + case 2: + obj.depth = reader.uint32() + break + case 3: { + const entry = PinAddResponse.PinAddResponse$metadataEntry.codec().decode(reader, reader.uint32()) + obj.metadata.set(entry.key, entry.value) + break + } + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PinAddResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PinAddResponse => { + return decodeMessage(buf, PinAddResponse.codec()) + } +} + +export interface PinRmOptions {} + +export namespace PinRmOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PinRmOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PinRmOptions => { + return decodeMessage(buf, PinRmOptions.codec()) + } +} + +export interface PinRmRequest {} + +export namespace PinRmRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PinRmRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PinRmRequest => { + return decodeMessage(buf, PinRmRequest.codec()) + } +} + +export interface PinRmResponse {} + +export namespace PinRmResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PinRmResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PinRmResponse => { + return decodeMessage(buf, PinRmResponse.codec()) + } +} + +export interface PinLsOptions { + cid?: Uint8Array +} + +export namespace PinLsOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.cid != null) { + w.uint32(10) + w.bytes(obj.cid) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PinLsOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PinLsOptions => { + return decodeMessage(buf, PinLsOptions.codec()) + } +} + +export interface PinLsRequest {} + +export namespace PinLsRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PinLsRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PinLsRequest => { + return decodeMessage(buf, PinLsRequest.codec()) + } +} + +export interface PinLsResponse { + cid: Uint8Array + depth: number + metadata: Map +} + +export namespace PinLsResponse { + export interface PinLsResponse$metadataEntry { + key: string + value: string + } + + export namespace PinLsResponse$metadataEntry { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if ((obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key) + } + + if ((obj.value != null && obj.value !== '')) { + w.uint32(18) + w.string(obj.value) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + key: '', + value: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.key = reader.string() + break + case 2: + obj.value = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PinLsResponse$metadataEntry.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PinLsResponse$metadataEntry => { + return decodeMessage(buf, PinLsResponse$metadataEntry.codec()) + } + } + + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if ((obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid) + } + + if ((obj.depth != null && obj.depth !== 0)) { + w.uint32(16) + w.uint32(obj.depth) + } + + if (obj.metadata != null && obj.metadata.size !== 0) { + for (const [key, value] of obj.metadata.entries()) { + w.uint32(26) + PinLsResponse.PinLsResponse$metadataEntry.codec().encode({ key, value }, w) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0), + depth: 0, + metadata: new Map() + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + case 2: + obj.depth = reader.uint32() + break + case 3: { + const entry = PinLsResponse.PinLsResponse$metadataEntry.codec().decode(reader, reader.uint32()) + obj.metadata.set(entry.key, entry.value) + break + } + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PinLsResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PinLsResponse => { + return decodeMessage(buf, PinLsResponse.codec()) + } +} + +export interface IsPinnedOptions {} + +export namespace IsPinnedOptions { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, IsPinnedOptions.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): IsPinnedOptions => { + return decodeMessage(buf, IsPinnedOptions.codec()) + } +} + +export interface IsPinnedRequest { + cid: Uint8Array +} + +export namespace IsPinnedRequest { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if ((obj.cid != null && obj.cid.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.cid) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + cid: new Uint8Array(0) + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.cid = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, IsPinnedRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): IsPinnedRequest => { + return decodeMessage(buf, IsPinnedRequest.codec()) + } +} + +export interface IsPinnedResponse { + pinned: boolean +} + +export namespace IsPinnedResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if ((obj.pinned != null && obj.pinned !== false)) { + w.uint32(8) + w.bool(obj.pinned) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + pinned: false + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.pinned = reader.bool() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, IsPinnedResponse.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): IsPinnedResponse => { + return decodeMessage(buf, IsPinnedResponse.codec()) + } +} diff --git a/packages/rpc-protocol/src/root.proto b/packages/rpc-protocol/src/root.proto index 31fbecd..b025748 100644 --- a/packages/rpc-protocol/src/root.proto +++ b/packages/rpc-protocol/src/root.proto @@ -1,13 +1,13 @@ syntax = "proto3"; -message InfoOptions { - optional string peer_id = 1; +message GcOptions { + } -message InfoResponse { - string peer_id = 1; - repeated string multiaddrs = 2; - string agent_version = 3; - string protocol_version = 4; - repeated string protocols = 5; +message GcRequest { + +} + +message GcResponse { + } diff --git a/packages/rpc-protocol/src/root.ts b/packages/rpc-protocol/src/root.ts index 40b24ef..4a9723e 100644 --- a/packages/rpc-protocol/src/root.ts +++ b/packages/rpc-protocol/src/root.ts @@ -8,25 +8,18 @@ import { encodeMessage, decodeMessage, message } from 'protons-runtime' import type { Codec } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' -export interface InfoOptions { - peerId?: string -} +export interface GcOptions {} -export namespace InfoOptions { - let _codec: Codec +export namespace GcOptions { + let _codec: Codec - export const codec = (): Codec => { + export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, w, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { w.fork() } - if (obj.peerId != null) { - w.uint32(10) - w.string(obj.peerId) - } - if (opts.lengthDelimited !== false) { w.ldelim() } @@ -39,9 +32,6 @@ export namespace InfoOptions { const tag = reader.uint32() switch (tag >>> 3) { - case 1: - obj.peerId = reader.string() - break default: reader.skipType(tag & 7) break @@ -55,73 +45,78 @@ export namespace InfoOptions { return _codec } - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, InfoOptions.codec()) + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GcOptions.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): InfoOptions => { - return decodeMessage(buf, InfoOptions.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList): GcOptions => { + return decodeMessage(buf, GcOptions.codec()) } } -export interface InfoResponse { - peerId: string - multiaddrs: string[] - agentVersion: string - protocolVersion: string - protocols: string[] -} +export interface GcRequest {} -export namespace InfoResponse { - let _codec: Codec +export namespace GcRequest { + let _codec: Codec - export const codec = (): Codec => { + export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, w, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { w.fork() } - if (opts.writeDefaults === true || (obj.peerId != null && obj.peerId !== '')) { - w.uint32(10) - w.string(obj.peerId ?? '') + if (opts.lengthDelimited !== false) { + w.ldelim() } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length - if (obj.multiaddrs != null) { - for (const value of obj.multiaddrs) { - w.uint32(18) - w.string(value) + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7) + break } } - if (opts.writeDefaults === true || (obj.agentVersion != null && obj.agentVersion !== '')) { - w.uint32(26) - w.string(obj.agentVersion ?? '') - } + return obj + }) + } - if (opts.writeDefaults === true || (obj.protocolVersion != null && obj.protocolVersion !== '')) { - w.uint32(34) - w.string(obj.protocolVersion ?? '') - } + return _codec + } - if (obj.protocols != null) { - for (const value of obj.protocols) { - w.uint32(42) - w.string(value) - } + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GcRequest.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): GcRequest => { + return decodeMessage(buf, GcRequest.codec()) + } +} + +export interface GcResponse {} + +export namespace GcResponse { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() } if (opts.lengthDelimited !== false) { w.ldelim() } }, (reader, length) => { - const obj: any = { - peerId: '', - multiaddrs: [], - agentVersion: '', - protocolVersion: '', - protocols: [] - } + const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -129,21 +124,6 @@ export namespace InfoResponse { const tag = reader.uint32() switch (tag >>> 3) { - case 1: - obj.peerId = reader.string() - break - case 2: - obj.multiaddrs.push(reader.string()) - break - case 3: - obj.agentVersion = reader.string() - break - case 4: - obj.protocolVersion = reader.string() - break - case 5: - obj.protocols.push(reader.string()) - break default: reader.skipType(tag & 7) break @@ -157,11 +137,11 @@ export namespace InfoResponse { return _codec } - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, InfoResponse.codec()) + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, GcResponse.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): InfoResponse => { - return decodeMessage(buf, InfoResponse.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList): GcResponse => { + return decodeMessage(buf, GcResponse.codec()) } } diff --git a/packages/rpc-protocol/src/rpc.ts b/packages/rpc-protocol/src/rpc.ts index 659c712..f117e8d 100644 --- a/packages/rpc-protocol/src/rpc.ts +++ b/packages/rpc-protocol/src/rpc.ts @@ -25,24 +25,24 @@ export namespace RPCCallRequest { w.fork() } - if (opts.writeDefaults === true || (obj.resource != null && obj.resource !== '')) { + if ((obj.resource != null && obj.resource !== '')) { w.uint32(10) - w.string(obj.resource ?? '') + w.string(obj.resource) } - if (opts.writeDefaults === true || (obj.method != null && obj.method !== '')) { + if ((obj.method != null && obj.method !== '')) { w.uint32(18) - w.string(obj.method ?? '') + w.string(obj.method) } - if (opts.writeDefaults === true || (obj.authorization != null && obj.authorization !== '')) { + if ((obj.authorization != null && obj.authorization !== '')) { w.uint32(26) - w.string(obj.authorization ?? '') + w.string(obj.authorization) } - if (opts.writeDefaults === true || (obj.options != null && obj.options.byteLength > 0)) { + if ((obj.options != null && obj.options.byteLength > 0)) { w.uint32(34) - w.bytes(obj.options ?? new Uint8Array(0)) + w.bytes(obj.options) } if (opts.lengthDelimited !== false) { @@ -130,14 +130,14 @@ export namespace RPCCallMessage { w.fork() } - if (opts.writeDefaults === true || (obj.type != null && __RPCCallMessageTypeValues[obj.type] !== 0)) { + if (obj.type != null && __RPCCallMessageTypeValues[obj.type] !== 0) { w.uint32(8) - RPCCallMessageType.codec().encode(obj.type ?? RPCCallMessageType.RPC_CALL_DONE, w) + RPCCallMessageType.codec().encode(obj.type, w) } - if (opts.writeDefaults === true || (obj.message != null && obj.message.byteLength > 0)) { + if ((obj.message != null && obj.message.byteLength > 0)) { w.uint32(18) - w.bytes(obj.message ?? new Uint8Array(0)) + w.bytes(obj.message) } if (opts.lengthDelimited !== false) { @@ -287,14 +287,14 @@ export namespace RPCCallProgress { w.fork() } - if (opts.writeDefaults === true || (obj.key != null && obj.key !== '')) { + if ((obj.key != null && obj.key !== '')) { w.uint32(10) - w.string(obj.key ?? '') + w.string(obj.key) } - if (opts.writeDefaults === true || (obj.value != null && obj.value !== '')) { + if ((obj.value != null && obj.value !== '')) { w.uint32(18) - w.string(obj.value ?? '') + w.string(obj.value) } if (opts.lengthDelimited !== false) { @@ -349,17 +349,15 @@ export namespace RPCCallProgress { w.fork() } - if (opts.writeDefaults === true || (obj.event != null && obj.event !== '')) { + if ((obj.event != null && obj.event !== '')) { w.uint32(10) - w.string(obj.event ?? '') + w.string(obj.event) } if (obj.data != null && obj.data.size !== 0) { for (const [key, value] of obj.data.entries()) { w.uint32(34) - RPCCallProgress.RPCCallProgress$dataEntry.codec().encode({ key, value }, w, { - writeDefaults: true - }) + RPCCallProgress.RPCCallProgress$dataEntry.codec().encode({ key, value }, w) } } diff --git a/packages/rpc-server/package.json b/packages/rpc-server/package.json index d1f1ba8..b3b8b67 100644 --- a/packages/rpc-server/package.json +++ b/packages/rpc-server/package.json @@ -144,9 +144,9 @@ "@libp2p/interface-peer-id": "^2.0.1", "@libp2p/logger": "^2.0.5", "@libp2p/peer-id": "^2.0.0", - "@multiformats/multiaddr": "^11.1.5", + "@multiformats/multiaddr": "^12.1.0", "@ucans/ucans": "^0.11.0-alpha", - "it-pb-stream": "^2.0.3", + "it-pb-stream": "^3.2.0", "multiformats": "^11.0.1", "uint8arrays": "^4.0.3" }, diff --git a/packages/rpc-server/src/handlers/blockstore/close.ts b/packages/rpc-server/src/handlers/blockstore/close.ts deleted file mode 100644 index b5e2956..0000000 --- a/packages/rpc-server/src/handlers/blockstore/close.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { RPCServerConfig, Service } from '../../index.js' - -export function createBlockstoreClose (config: RPCServerConfig): Service { - return { - async handle ({ options, stream, signal }): Promise { - await config.helia.blockstore.close() - } - } -} diff --git a/packages/rpc-server/src/handlers/blockstore/get-all.ts b/packages/rpc-server/src/handlers/blockstore/get-all.ts new file mode 100644 index 0000000..57a1c13 --- /dev/null +++ b/packages/rpc-server/src/handlers/blockstore/get-all.ts @@ -0,0 +1,25 @@ +import { GetAllOptions, GetAllResponse } from '@helia/rpc-protocol/blockstore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' + +export function createBlockstoreGetAll (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = GetAllOptions.decode(options) + + for await (const { cid, block } of config.helia.blockstore.getAll({ + signal, + ...opts + })) { + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: GetAllResponse.encode({ + cid: cid.bytes, + block: block + }) + }, + RPCCallMessage) + } + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/get-many.ts b/packages/rpc-server/src/handlers/blockstore/get-many.ts index 0188afa..6b2ef22 100644 --- a/packages/rpc-server/src/handlers/blockstore/get-many.ts +++ b/packages/rpc-server/src/handlers/blockstore/get-many.ts @@ -1,4 +1,4 @@ -import { DeleteManyOptions, DeleteManyRequest, DeleteManyResponse } from '@helia/rpc-protocol/blockstore' +import { GetManyOptions, GetManyRequest, GetManyResponse } from '@helia/rpc-protocol/blockstore' import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' import type { RPCServerConfig, Service } from '../../index.js' import { CID } from 'multiformats/cid' @@ -6,12 +6,12 @@ import { CID } from 'multiformats/cid' export function createBlockstoreGetMany (config: RPCServerConfig): Service { return { async handle ({ options, stream, signal }): Promise { - const opts = DeleteManyOptions.decode(options) + const opts = GetManyOptions.decode(options) - for await (const cid of config.helia.blockstore.deleteMany( + for await (const block of config.helia.blockstore.getMany( (async function * () { while (true) { - const request = await stream.readPB(DeleteManyRequest) + const request = await stream.readPB(GetManyRequest) yield CID.decode(request.cid) } @@ -21,8 +21,8 @@ export function createBlockstoreGetMany (config: RPCServerConfig): Service { })) { stream.writePB({ type: RPCCallMessageType.RPC_CALL_MESSAGE, - message: DeleteManyResponse.encode({ - cid: cid.bytes + message: GetManyResponse.encode({ + block }) }, RPCCallMessage) diff --git a/packages/rpc-server/src/handlers/blockstore/open.ts b/packages/rpc-server/src/handlers/blockstore/open.ts deleted file mode 100644 index 606b6b9..0000000 --- a/packages/rpc-server/src/handlers/blockstore/open.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { RPCServerConfig, Service } from '../../index.js' - -export function createBlockstoreOpen (config: RPCServerConfig): Service { - return { - async handle ({ options, stream, signal }): Promise { - await config.helia.blockstore.open() - } - } -} diff --git a/packages/rpc-server/src/handlers/blockstore/batch.ts b/packages/rpc-server/src/handlers/datastore/batch.ts similarity index 69% rename from packages/rpc-server/src/handlers/blockstore/batch.ts rename to packages/rpc-server/src/handlers/datastore/batch.ts index f76dd93..50cbea6 100644 --- a/packages/rpc-server/src/handlers/blockstore/batch.ts +++ b/packages/rpc-server/src/handlers/datastore/batch.ts @@ -1,11 +1,11 @@ -import { BatchRequest, BatchRequestDelete, BatchRequestPut, BatchRequestType } from '@helia/rpc-protocol/blockstore' +import { BatchRequest, BatchRequestDelete, BatchRequestPut, BatchRequestType } from '@helia/rpc-protocol/datastore' import type { RPCServerConfig, Service } from '../../index.js' -import { CID } from 'multiformats/cid' +import { Key } from 'interface-datastore' -export function createBlockstoreBatch (config: RPCServerConfig): Service { +export function createDatastoreBatch (config: RPCServerConfig): Service { return { async handle ({ options, stream, signal }): Promise { - const batch = config.helia.blockstore.batch() + const batch = config.helia.datastore.batch() while (true) { const request = await stream.readPB(BatchRequest) @@ -22,17 +22,17 @@ export function createBlockstoreBatch (config: RPCServerConfig): Service { switch (request.type) { case BatchRequestType.BATCH_REQUEST_PUT: putMessage = BatchRequestPut.decode(request.message) - batch.put(CID.decode(putMessage.cid), putMessage.block) + batch.put(new Key(putMessage.key), putMessage.value) break case BatchRequestType.BATCH_REQUEST_DELETE: deleteMessage = BatchRequestDelete.decode(request.message) - batch.delete(CID.decode(deleteMessage.cid)) + batch.delete(new Key(deleteMessage.key)) break case BatchRequestType.BATCH_REQUEST_COMMIT: await batch.commit() return default: - throw new Error('Unkown batch message type') + throw new Error('Unknown batch message type') } } } diff --git a/packages/rpc-server/src/handlers/datastore/delete-many.ts b/packages/rpc-server/src/handlers/datastore/delete-many.ts new file mode 100644 index 0000000..f8e13b1 --- /dev/null +++ b/packages/rpc-server/src/handlers/datastore/delete-many.ts @@ -0,0 +1,32 @@ +import { DeleteManyOptions, DeleteManyRequest, DeleteManyResponse } from '@helia/rpc-protocol/datastore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { Key } from 'interface-datastore' + +export function createDatastoreDeleteMany (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = DeleteManyOptions.decode(options) + + for await (const key of config.helia.datastore.deleteMany( + (async function * () { + while (true) { + const request = await stream.readPB(DeleteManyRequest) + + yield new Key(request.key) + } + })(), { + signal, + ...opts + })) { + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: DeleteManyResponse.encode({ + key: key.toString() + }) + }, + RPCCallMessage) + } + } + } +} diff --git a/packages/rpc-server/src/handlers/datastore/delete.ts b/packages/rpc-server/src/handlers/datastore/delete.ts new file mode 100644 index 0000000..c8f3eae --- /dev/null +++ b/packages/rpc-server/src/handlers/datastore/delete.ts @@ -0,0 +1,26 @@ +import { DeleteOptions, DeleteRequest, DeleteResponse } from '@helia/rpc-protocol/datastore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { Key } from 'interface-datastore' + +export function createDatastoreDelete (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = DeleteOptions.decode(options) + const request = await stream.readPB(DeleteRequest) + const key = new Key(request.key) + + await config.helia.datastore.delete(key, { + signal, + ...opts + }) + + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: DeleteResponse.encode({ + }) + }, + RPCCallMessage) + } + } +} diff --git a/packages/rpc-server/src/handlers/datastore/get-many.ts b/packages/rpc-server/src/handlers/datastore/get-many.ts new file mode 100644 index 0000000..f716987 --- /dev/null +++ b/packages/rpc-server/src/handlers/datastore/get-many.ts @@ -0,0 +1,32 @@ +import { DeleteManyOptions, DeleteManyRequest, DeleteManyResponse } from '@helia/rpc-protocol/datastore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { Key } from 'interface-datastore' + +export function createDatastoreGetMany (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = DeleteManyOptions.decode(options) + + for await (const key of config.helia.datastore.deleteMany( + (async function * () { + while (true) { + const request = await stream.readPB(DeleteManyRequest) + + yield new Key(request.key) + } + })(), { + signal, + ...opts + })) { + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: DeleteManyResponse.encode({ + key: key.toString() + }) + }, + RPCCallMessage) + } + } + } +} diff --git a/packages/rpc-server/src/handlers/datastore/get.ts b/packages/rpc-server/src/handlers/datastore/get.ts new file mode 100644 index 0000000..b01a018 --- /dev/null +++ b/packages/rpc-server/src/handlers/datastore/get.ts @@ -0,0 +1,27 @@ +import { GetOptions, GetRequest, GetResponse } from '@helia/rpc-protocol/datastore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { Key } from 'interface-datastore' + +export function createDatastoreGet (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = GetOptions.decode(options) + const request = await stream.readPB(GetRequest) + const key = new Key(request.key) + + const value = await config.helia.datastore.get(key, { + signal, + ...opts + }) + + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: GetResponse.encode({ + value + }) + }, + RPCCallMessage) + } + } +} diff --git a/packages/rpc-server/src/handlers/datastore/has.ts b/packages/rpc-server/src/handlers/datastore/has.ts new file mode 100644 index 0000000..a1b57b2 --- /dev/null +++ b/packages/rpc-server/src/handlers/datastore/has.ts @@ -0,0 +1,27 @@ +import { HasOptions, HasRequest, HasResponse } from '@helia/rpc-protocol/datastore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { Key } from 'interface-datastore' + +export function createDatastoreHas (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = HasOptions.decode(options) + const request = await stream.readPB(HasRequest) + const key = new Key(request.key) + + const has = await config.helia.datastore.has(key, { + signal, + ...opts + }) + + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: HasResponse.encode({ + has + }) + }, + RPCCallMessage) + } + } +} diff --git a/packages/rpc-server/src/handlers/datastore/put-many.ts b/packages/rpc-server/src/handlers/datastore/put-many.ts new file mode 100644 index 0000000..e70a069 --- /dev/null +++ b/packages/rpc-server/src/handlers/datastore/put-many.ts @@ -0,0 +1,32 @@ +import { DeleteManyOptions, DeleteManyRequest, DeleteManyResponse } from '@helia/rpc-protocol/datastore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { Key } from 'interface-datastore' + +export function createDatastorePutMany (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = DeleteManyOptions.decode(options) + + for await (const key of config.helia.datastore.deleteMany( + (async function * () { + while (true) { + const request = await stream.readPB(DeleteManyRequest) + + yield new Key(request.key) + } + })(), { + signal, + ...opts + })) { + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: DeleteManyResponse.encode({ + key: key.toString() + }) + }, + RPCCallMessage) + } + } + } +} diff --git a/packages/rpc-server/src/handlers/datastore/put.ts b/packages/rpc-server/src/handlers/datastore/put.ts new file mode 100644 index 0000000..52142d7 --- /dev/null +++ b/packages/rpc-server/src/handlers/datastore/put.ts @@ -0,0 +1,26 @@ +import { PutOptions, PutRequest, PutResponse } from '@helia/rpc-protocol/datastore' +import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' +import type { RPCServerConfig, Service } from '../../index.js' +import { Key } from 'interface-datastore' + +export function createDatastorePut (config: RPCServerConfig): Service { + return { + async handle ({ options, stream, signal }): Promise { + const opts = PutOptions.decode(options) + const request = await stream.readPB(PutRequest) + const key = new Key(request.key) + + await config.helia.datastore.put(key, request.value, { + signal, + ...opts + }) + + stream.writePB({ + type: RPCCallMessageType.RPC_CALL_MESSAGE, + message: PutResponse.encode({ + }) + }, + RPCCallMessage) + } + } +} diff --git a/packages/rpc-server/src/handlers/blockstore/query-keys.ts b/packages/rpc-server/src/handlers/datastore/query-keys.ts similarity index 74% rename from packages/rpc-server/src/handlers/blockstore/query-keys.ts rename to packages/rpc-server/src/handlers/datastore/query-keys.ts index 0afb079..ebafd2a 100644 --- a/packages/rpc-server/src/handlers/blockstore/query-keys.ts +++ b/packages/rpc-server/src/handlers/datastore/query-keys.ts @@ -1,13 +1,13 @@ -import { QueryKeysOptions, QueryKeysResponse } from '@helia/rpc-protocol/blockstore' +import { QueryKeysOptions, QueryKeysResponse } from '@helia/rpc-protocol/datastore' import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' import type { RPCServerConfig, Service } from '../../index.js' -export function createBlockstoreQueryKeys (config: RPCServerConfig): Service { +export function createDatastoreQueryKeys (config: RPCServerConfig): Service { return { async handle ({ options, stream, signal }): Promise { const opts = QueryKeysOptions.decode(options) - for await (const cid of config.helia.blockstore.queryKeys({ + for await (const key of config.helia.datastore.queryKeys({ ...opts }, { signal @@ -15,7 +15,7 @@ export function createBlockstoreQueryKeys (config: RPCServerConfig): Service { stream.writePB({ type: RPCCallMessageType.RPC_CALL_MESSAGE, message: QueryKeysResponse.encode({ - key: cid.bytes + key: key.toString() }) }, RPCCallMessage) diff --git a/packages/rpc-server/src/handlers/blockstore/query.ts b/packages/rpc-server/src/handlers/datastore/query.ts similarity index 75% rename from packages/rpc-server/src/handlers/blockstore/query.ts rename to packages/rpc-server/src/handlers/datastore/query.ts index 3c41c85..c8ab31f 100644 --- a/packages/rpc-server/src/handlers/blockstore/query.ts +++ b/packages/rpc-server/src/handlers/datastore/query.ts @@ -1,13 +1,13 @@ -import { QueryOptions, QueryResponse } from '@helia/rpc-protocol/blockstore' +import { QueryOptions, QueryResponse } from '@helia/rpc-protocol/datastore' import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' import type { RPCServerConfig, Service } from '../../index.js' -export function createBlockstoreQuery (config: RPCServerConfig): Service { +export function createDatastoreQuery (config: RPCServerConfig): Service { return { async handle ({ options, stream, signal }): Promise { const opts = QueryOptions.decode(options) - for await (const { key, value } of config.helia.blockstore.query({ + for await (const { key, value } of config.helia.datastore.query({ ...opts }, { signal @@ -15,7 +15,7 @@ export function createBlockstoreQuery (config: RPCServerConfig): Service { stream.writePB({ type: RPCCallMessageType.RPC_CALL_MESSAGE, message: QueryResponse.encode({ - key: key.bytes, + key: key.toString(), value }) }, diff --git a/packages/rpc-server/src/handlers/index.ts b/packages/rpc-server/src/handlers/index.ts index 1856e16..ba8ad0a 100644 --- a/packages/rpc-server/src/handlers/index.ts +++ b/packages/rpc-server/src/handlers/index.ts @@ -1,35 +1,45 @@ import type { RPCServerConfig, Service } from '../index.js' -import { createInfo } from './info.js' import { createAuthorizationGet } from './authorization/get.js' import { createBlockstoreDelete } from './blockstore/delete.js' import { createBlockstoreGet } from './blockstore/get.js' import { createBlockstoreHas } from './blockstore/has.js' import { createBlockstorePut } from './blockstore/put.js' import { createBlockstoreDeleteMany } from './blockstore/delete-many.js' +import { createBlockstoreGetAll } from './blockstore/get-all.js' import { createBlockstoreGetMany } from './blockstore/get-many.js' -import { createBlockstoreBatch } from './blockstore/batch.js' -import { createBlockstoreClose } from './blockstore/close.js' -import { createBlockstoreOpen } from './blockstore/open.js' import { createBlockstorePutMany } from './blockstore/put-many.js' -import { createBlockstoreQueryKeys } from './blockstore/query-keys.js' -import { createBlockstoreQuery } from './blockstore/query.js' +import { createDatastoreBatch } from './datastore/batch.js' +import { createDatastoreDelete } from './datastore/delete.js' +import { createDatastoreGet } from './datastore/get.js' +import { createDatastoreHas } from './datastore/has.js' +import { createDatastorePut } from './datastore/put.js' +import { createDatastoreDeleteMany } from './datastore/delete-many.js' +import { createDatastoreGetMany } from './datastore/get-many.js' +import { createDatastorePutMany } from './datastore/put-many.js' +import { createDatastoreQueryKeys } from './datastore/query-keys.js' +import { createDatastoreQuery } from './datastore/query.js' export function createServices (config: RPCServerConfig): Record { const services: Record = { '/authorization/get': createAuthorizationGet(config), - '/blockstore/batch': createBlockstoreBatch(config), - '/blockstore/close': createBlockstoreClose(config), '/blockstore/delete-many': createBlockstoreDeleteMany(config), '/blockstore/delete': createBlockstoreDelete(config), + '/blockstore/get-all': createBlockstoreGetAll(config), '/blockstore/get-many': createBlockstoreGetMany(config), '/blockstore/get': createBlockstoreGet(config), '/blockstore/has': createBlockstoreHas(config), - '/blockstore/open': createBlockstoreOpen(config), '/blockstore/put-many': createBlockstorePutMany(config), '/blockstore/put': createBlockstorePut(config), - '/blockstore/query-keys': createBlockstoreQueryKeys(config), - '/blockstore/query': createBlockstoreQuery(config), - '/info': createInfo(config) + '/datastore/batch': createDatastoreBatch(config), + '/datastore/delete-many': createDatastoreDeleteMany(config), + '/datastore/delete': createDatastoreDelete(config), + '/datastore/get-many': createDatastoreGetMany(config), + '/datastore/get': createDatastoreGet(config), + '/datastore/has': createDatastoreHas(config), + '/datastore/put-many': createDatastorePutMany(config), + '/datastore/put': createDatastorePut(config), + '/datastore/query-keys': createDatastoreQueryKeys(config), + '/datastore/query': createDatastoreQuery(config) } return services diff --git a/packages/rpc-server/src/handlers/info.ts b/packages/rpc-server/src/handlers/info.ts deleted file mode 100644 index 0a496f7..0000000 --- a/packages/rpc-server/src/handlers/info.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { InfoOptions, InfoResponse } from '@helia/rpc-protocol/root' -import { RPCCallMessage, RPCCallMessageType } from '@helia/rpc-protocol/rpc' -import { peerIdFromString } from '@libp2p/peer-id' -import type { RPCServerConfig, Service } from '../index.js' - -export function createInfo (config: RPCServerConfig): Service { - return { - insecure: true, - async handle ({ options, stream, signal }): Promise { - const opts = InfoOptions.decode(options) - - const result = await config.helia.info({ - peerId: opts.peerId != null ? peerIdFromString(opts.peerId) : undefined, - signal - }) - - stream.writePB({ - type: RPCCallMessageType.RPC_CALL_MESSAGE, - message: InfoResponse.encode({ - ...result, - peerId: result.peerId.toString(), - multiaddrs: result.multiaddrs.map(ma => ma.toString()) - }) - }, RPCCallMessage) - } - } -} diff --git a/packages/rpc-server/src/index.ts b/packages/rpc-server/src/index.ts index ac66503..1d4600e 100644 --- a/packages/rpc-server/src/index.ts +++ b/packages/rpc-server/src/index.ts @@ -1,5 +1,4 @@ import type { Helia } from '@helia/interface' -import { HeliaError } from '@helia/interface/errors' import { logger } from '@libp2p/logger' import { HELIA_RPC_PROTOCOL } from '@helia/rpc-protocol' import { RPCCallRequest, RPCCallError, RPCCallMessageType, RPCCallMessage } from '@helia/rpc-protocol/rpc' @@ -37,9 +36,14 @@ export interface Service { handle: (args: ServiceArgs) => Promise } -class RPCError extends HeliaError { +class RPCError extends Error { + public code: string + constructor (message: string, code: string) { - super(message, 'RPCError', code) + super(message) + + this.name = 'RPCError' + this.code = code } } diff --git a/packages/rpc-server/src/utils/multiaddr-to-url.ts b/packages/rpc-server/src/utils/multiaddr-to-url.ts index a967130..7a9dc32 100644 --- a/packages/rpc-server/src/utils/multiaddr-to-url.ts +++ b/packages/rpc-server/src/utils/multiaddr-to-url.ts @@ -1,19 +1,18 @@ import type { Multiaddr } from '@multiformats/multiaddr' -import { InvalidParametersError } from '@helia/interface/errors' export function multiaddrToUrl (addr: Multiaddr): URL { const protoNames = addr.protoNames() if (protoNames.length !== 3) { - throw new InvalidParametersError('Helia RPC address format incorrect') + throw new Error('Helia RPC address format incorrect') } if (protoNames[0] !== 'ip4' && protoNames[0] !== 'ip6') { - throw new InvalidParametersError('Helia RPC address format incorrect') + throw new Error('Helia RPC address format incorrect') } if (protoNames[1] !== 'tcp' && protoNames[2] !== 'ws') { - throw new InvalidParametersError('Helia RPC address format incorrect') + throw new Error('Helia RPC address format incorrect') } const { host, port } = addr.toOptions() diff --git a/packages/unixfs-cli/package.json b/packages/unixfs-cli/package.json index 631e1ee..7c835a1 100644 --- a/packages/unixfs-cli/package.json +++ b/packages/unixfs-cli/package.json @@ -137,11 +137,11 @@ }, "dependencies": { "@helia/cli-utils": "~0.0.0", - "@helia/unixfs": "next", + "@helia/unixfs": "^1.2.0", "@libp2p/interfaces": "^3.3.1", - "ipfs-unixfs": "^9.0.0", - "ipfs-unixfs-exporter": "^10.0.0", - "ipfs-unixfs-importer": "^12.0.0", + "ipfs-unixfs": "^11.0.0", + "ipfs-unixfs-exporter": "^13.0.0", + "ipfs-unixfs-importer": "^15.0.0", "it-glob": "^2.0.0", "it-merge": "^2.0.0", "kleur": "^4.1.5", diff --git a/packages/unixfs-cli/src/commands/add.ts b/packages/unixfs-cli/src/commands/add.ts index b03ae38..bc174c0 100644 --- a/packages/unixfs-cli/src/commands/add.ts +++ b/packages/unixfs-cli/src/commands/add.ts @@ -1,11 +1,11 @@ -import { unixfs } from '@helia/unixfs' +import { AddOptions, unixfs } from '@helia/unixfs' import merge from 'it-merge' import path from 'node:path' import { globSource } from '../utils/glob-source.js' import fs from 'node:fs' import { dateToMtime } from '../utils/date-to-mtime.js' import type { Mtime } from 'ipfs-unixfs' -import type { ImportCandidate, UserImporterOptions } from 'ipfs-unixfs-importer' +import type { ImportCandidate } from 'ipfs-unixfs-importer' import type { Command } from '@helia/cli-utils' interface AddArgs { @@ -18,13 +18,13 @@ export const add: Command = { description: 'Add a file or directory to your helia node', example: '$ unixfs add path/to/file.txt', async execute ({ positionals, helia, stdout }) { - const options: UserImporterOptions = { + const options: AddOptions = { cidVersion: 1, rawLeaves: true } const fs = unixfs(helia) - for await (const result of fs.addStream(parsePositionals(positionals), options)) { + for await (const result of fs.addAll(parsePositionals(positionals), options)) { stdout.write(`${result.cid}\n`) } } diff --git a/packages/unixfs-cli/src/utils/date-to-mtime.ts b/packages/unixfs-cli/src/utils/date-to-mtime.ts index 642c34b..366e33e 100644 --- a/packages/unixfs-cli/src/utils/date-to-mtime.ts +++ b/packages/unixfs-cli/src/utils/date-to-mtime.ts @@ -2,10 +2,10 @@ import type { Mtime } from 'ipfs-unixfs' export function dateToMtime (date: Date): Mtime { const ms = date.getTime() - const secs = Math.floor(ms / 1000) + const secs = BigInt(Math.floor(ms / 1000)) return { secs, - nsecs: (ms - (secs * 1000)) * 1000 + nsecs: Number((BigInt(ms) - (secs * 1000n)) * 1000n) } } diff --git a/packages/unixfs-cli/src/utils/glob-source.ts b/packages/unixfs-cli/src/utils/glob-source.ts index 17de99a..6fe7920 100644 --- a/packages/unixfs-cli/src/utils/glob-source.ts +++ b/packages/unixfs-cli/src/utils/glob-source.ts @@ -76,7 +76,7 @@ export async function * globSource (cwd: string, pattern: string, options: GlobS const secs = Math.floor(ms / 1000) mtime = { - secs, + secs: BigInt(secs), nsecs: (ms - (secs * 1000)) * 1000 } }